Compare commits
95 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 011d44304a | |||
| a087be8afe | |||
| 483cbb6d46 | |||
| ec76cbd696 | |||
| a83d8c43fe | |||
| 5f25e37f7f | |||
| cafd27a60d | |||
| 5082bbbdf2 | |||
| d2ba21821f | |||
| 9bef7d64d5 | |||
| ca4352addd | |||
| 2906315fa7 | |||
| 6f036388b9 | |||
| 9cd4fd740e | |||
| c97acada76 | |||
| 19ff9062ad | |||
| e4f8243c9b | |||
| 1060e4a44a | |||
| 983edd5f76 | |||
| 5315165cf5 | |||
| ee3d87010e | |||
| 3ac91c4971 | |||
| 8e450934ab | |||
| bf193eec23 | |||
| c9104540aa | |||
| af60d84abe | |||
| 4345ce857f | |||
| 823572bf25 | |||
| e6bd63b3ec | |||
| bbf3d71e7e | |||
| 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 | |||
| 8a57dceb9d | |||
| 6c8867e1b3 | |||
| 20d86cbfde | |||
| 16a164e5b8 | |||
| 261f087b46 | |||
| c876350b05 | |||
| 58f3932241 | |||
| 350abc2838 | |||
| 5d4c9a5dd2 | |||
| b31d097b61 | |||
| 9f6c091706 | |||
| af2229eabe | |||
| b86946b396 | |||
| e1ae97d3ca | |||
| a7ac747776 | |||
| fab0f5bfe1 | |||
| f5f90bd235 | |||
| 603dc13d0f | |||
| e277c6a8dc | |||
| 5ff9d03f20 | |||
| 9ce9ca85be | |||
| 553c8ab719 | |||
| e768e9b06d | |||
| fd913a777c | |||
| ed9e6daec5 | |||
| ee6249a658 | |||
| b307304f46 | |||
| f028f82e11 | |||
| 719a3385f6 | |||
| 2adff11400 | |||
| 34fed6d547 | |||
| db2df75624 | |||
| 6b535752df | |||
| 556f87ff25 | |||
| fbebbd85eb | |||
| 4650c2a4ea | |||
| 5390fc3f1b | |||
| 0468dd5f02 |
@@ -0,0 +1,51 @@
|
|||||||
|
name: Build And Create Nuget Package
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ main ]
|
||||||
|
workflow_dispatch:
|
||||||
|
schedule:
|
||||||
|
- cron: '3 0 * * 0'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
main:
|
||||||
|
runs-on: ${{ matrix.environment }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
environment:
|
||||||
|
- macos-latest
|
||||||
|
- ubuntu-latest
|
||||||
|
- windows-latest
|
||||||
|
env:
|
||||||
|
DOTNET_NOLOGO: 1
|
||||||
|
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||||
|
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages
|
||||||
|
UNIT_TEST_PROJECT: QuestPDF.UnitTests/QuestPDF.UnitTests.csproj
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: 📝 Fetch Sources 📝
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: ⚙ Setup .NET 6.0 SDK ⚙
|
||||||
|
uses: actions/setup-dotnet@v1
|
||||||
|
with:
|
||||||
|
dotnet-version: '6.0.x'
|
||||||
|
|
||||||
|
- name: 🔄 Restore Nuget Packages 🔄
|
||||||
|
shell: bash
|
||||||
|
run: dotnet restore
|
||||||
|
|
||||||
|
- name: 🛠 Build Solution 🛠
|
||||||
|
shell: bash
|
||||||
|
run: dotnet build -c Release --no-restore
|
||||||
|
|
||||||
|
- name: Upload build artifacts
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: Build Package
|
||||||
|
path: |
|
||||||
|
**/*.nupkg
|
||||||
|
**/*.snupkg
|
||||||
|
!.nuget
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using NUnit.Framework;
|
||||||
|
using QuestPDF.Examples.Engine;
|
||||||
|
using QuestPDF.Fluent;
|
||||||
|
using QuestPDF.Helpers;
|
||||||
|
|
||||||
|
namespace QuestPDF.Examples
|
||||||
|
{
|
||||||
|
public class AlignmentExamples
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void Example()
|
||||||
|
{
|
||||||
|
RenderingTest
|
||||||
|
.Create()
|
||||||
|
.PageSize(400, 200)
|
||||||
|
.ProduceImages()
|
||||||
|
.ShowResults()
|
||||||
|
.Render(container =>
|
||||||
|
{
|
||||||
|
container
|
||||||
|
.Padding(25)
|
||||||
|
.Border(1)
|
||||||
|
.AlignBottom()
|
||||||
|
.Background(Colors.Grey.Lighten1)
|
||||||
|
.Text("Test");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,408 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using QuestPDF.Drawing.Exceptions;
|
||||||
|
using QuestPDF.Elements;
|
||||||
|
using QuestPDF.Examples.Engine;
|
||||||
|
using QuestPDF.Fluent;
|
||||||
|
using QuestPDF.Helpers;
|
||||||
|
using QuestPDF.Infrastructure;
|
||||||
|
|
||||||
|
namespace QuestPDF.Examples
|
||||||
|
{
|
||||||
|
public class ContentDirectionExamples
|
||||||
|
{
|
||||||
|
private Action<IContainer> ContentDirectionTemplate(Action<IContainer> content)
|
||||||
|
{
|
||||||
|
return container =>
|
||||||
|
{
|
||||||
|
container
|
||||||
|
.MinimalBox()
|
||||||
|
.ExtendHorizontal()
|
||||||
|
.Table(table =>
|
||||||
|
{
|
||||||
|
table.ColumnsDefinition(columns =>
|
||||||
|
{
|
||||||
|
columns.RelativeColumn();
|
||||||
|
columns.ConstantColumn(1);
|
||||||
|
columns.RelativeColumn();
|
||||||
|
});
|
||||||
|
|
||||||
|
table.Header(header =>
|
||||||
|
{
|
||||||
|
header.Cell().ContentFromLeftToRight().Element(HeaderCell("Left-to-right"));
|
||||||
|
header.Cell().LineVertical(1).LineColor(Colors.Grey.Medium);
|
||||||
|
header.Cell().ContentFromRightToLeft().Element(HeaderCell("Right-to-left"));
|
||||||
|
|
||||||
|
static Action<IContainer> HeaderCell(string label)
|
||||||
|
{
|
||||||
|
return container => container
|
||||||
|
.BorderColor(Colors.Grey.Medium)
|
||||||
|
.Background(Colors.Grey.Lighten2)
|
||||||
|
.PaddingHorizontal(15)
|
||||||
|
.PaddingVertical(5)
|
||||||
|
.Text(label)
|
||||||
|
.FontSize(18)
|
||||||
|
.SemiBold();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
table.Cell().Element(TestCell).ContentFromLeftToRight().Element(content);
|
||||||
|
table.Cell().LineVertical(1).LineColor(Colors.Grey.Medium);
|
||||||
|
table.Cell().Element(TestCell).ContentFromRightToLeft().Element(content);
|
||||||
|
|
||||||
|
static IContainer TestCell(IContainer container)
|
||||||
|
{
|
||||||
|
return container.Padding(15);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Page()
|
||||||
|
{
|
||||||
|
RenderingTest
|
||||||
|
.Create()
|
||||||
|
.ProduceImages()
|
||||||
|
.ShowResults()
|
||||||
|
.EnableDebugging()
|
||||||
|
.RenderDocument(document =>
|
||||||
|
{
|
||||||
|
document.Page(page =>
|
||||||
|
{
|
||||||
|
page.Size(PageSizes.A5);
|
||||||
|
page.Margin(20);
|
||||||
|
page.PageColor(Colors.White);
|
||||||
|
|
||||||
|
page.DefaultTextStyle(x => x.FontFamily("Calibri").FontSize(20));
|
||||||
|
page.ContentFromRightToLeft();
|
||||||
|
|
||||||
|
page.Content().Column(column =>
|
||||||
|
{
|
||||||
|
column.Spacing(20);
|
||||||
|
|
||||||
|
column.Item()
|
||||||
|
.Text("مثال على الفاتورة") // example invoice
|
||||||
|
.FontSize(32).FontColor(Colors.Blue.Darken2).SemiBold();
|
||||||
|
|
||||||
|
column.Item().Table(table =>
|
||||||
|
{
|
||||||
|
table.ColumnsDefinition(columns =>
|
||||||
|
{
|
||||||
|
columns.RelativeColumn();
|
||||||
|
columns.ConstantColumn(75);
|
||||||
|
columns.ConstantColumn(100);
|
||||||
|
});
|
||||||
|
|
||||||
|
table.Cell().Element(HeaderStyle).Text("وصف السلعة"); // item description
|
||||||
|
table.Cell().Element(HeaderStyle).Text("كمية"); // quantity
|
||||||
|
table.Cell().Element(HeaderStyle).Text("سعر"); // price
|
||||||
|
|
||||||
|
var items = new[]
|
||||||
|
{
|
||||||
|
"دورة البرمجة", // programming course
|
||||||
|
"دورة تصميم الرسومات", // graphics design course
|
||||||
|
"تحليل وتصميم الخوارزميات", // analysis and design of algorithms
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
var price = Placeholders.Random.NextDouble() * 100;
|
||||||
|
|
||||||
|
table.Cell().Text(item);
|
||||||
|
table.Cell().Text(Placeholders.Random.Next(1, 10));
|
||||||
|
table.Cell().Text($"USD${price:F2}");
|
||||||
|
}
|
||||||
|
|
||||||
|
static IContainer HeaderStyle(IContainer x) => x.BorderBottom(1).PaddingVertical(5);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Column()
|
||||||
|
{
|
||||||
|
RenderingTest
|
||||||
|
.Create()
|
||||||
|
.PageSize(PageSizes.A4)
|
||||||
|
.ProduceImages()
|
||||||
|
.ShowResults()
|
||||||
|
.EnableDebugging()
|
||||||
|
.Render(ContentDirectionTemplate(Content));
|
||||||
|
|
||||||
|
void Content(IContainer container)
|
||||||
|
{
|
||||||
|
container.Column(column =>
|
||||||
|
{
|
||||||
|
column.Spacing(5);
|
||||||
|
|
||||||
|
column.Item().Height(50).Width(50).Background(Colors.Red.Lighten1);
|
||||||
|
column.Item().Height(50).Width(100).Background(Colors.Green.Lighten1);
|
||||||
|
column.Item().Height(50).Width(150).Background(Colors.Blue.Lighten1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Row()
|
||||||
|
{
|
||||||
|
RenderingTest
|
||||||
|
.Create()
|
||||||
|
.PageSize(PageSizes.A4)
|
||||||
|
.ProduceImages()
|
||||||
|
.ShowResults()
|
||||||
|
.EnableDebugging()
|
||||||
|
.Render(ContentDirectionTemplate(Content));
|
||||||
|
|
||||||
|
void Content(IContainer container)
|
||||||
|
{
|
||||||
|
container.Row(row =>
|
||||||
|
{
|
||||||
|
row.Spacing(5);
|
||||||
|
|
||||||
|
row.AutoItem().Height(50).Width(50).Background(Colors.Red.Lighten1);
|
||||||
|
row.AutoItem().Height(50).Width(50).Background(Colors.Green.Lighten1);
|
||||||
|
row.AutoItem().Height(50).Width(75).Background(Colors.Blue.Lighten1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Table()
|
||||||
|
{
|
||||||
|
RenderingTest
|
||||||
|
.Create()
|
||||||
|
.PageSize(PageSizes.A4)
|
||||||
|
.ProduceImages()
|
||||||
|
.ShowResults()
|
||||||
|
.EnableDebugging()
|
||||||
|
.Render(ContentDirectionTemplate(Content));
|
||||||
|
|
||||||
|
void Content(IContainer container)
|
||||||
|
{
|
||||||
|
container.Table(table =>
|
||||||
|
{
|
||||||
|
table.ColumnsDefinition(columns =>
|
||||||
|
{
|
||||||
|
columns.RelativeColumn();
|
||||||
|
columns.RelativeColumn();
|
||||||
|
columns.RelativeColumn();
|
||||||
|
});
|
||||||
|
|
||||||
|
table.Cell().Height(50).Background(Colors.Red.Lighten1);
|
||||||
|
table.Cell().Height(50).Background(Colors.Green.Lighten1);
|
||||||
|
table.Cell().Height(50).Background(Colors.Blue.Lighten1);
|
||||||
|
table.Cell().ColumnSpan(2).Height(50).Background(Colors.Orange.Lighten1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Constrained()
|
||||||
|
{
|
||||||
|
RenderingTest
|
||||||
|
.Create()
|
||||||
|
.PageSize(PageSizes.A4)
|
||||||
|
.ProduceImages()
|
||||||
|
.ShowResults()
|
||||||
|
.EnableDebugging()
|
||||||
|
.Render(ContentDirectionTemplate(Content));
|
||||||
|
|
||||||
|
void Content(IContainer container)
|
||||||
|
{
|
||||||
|
container.Width(50).Height(50).Background(Colors.Red.Lighten1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Unconstrained()
|
||||||
|
{
|
||||||
|
RenderingTest
|
||||||
|
.Create()
|
||||||
|
.PageSize(PageSizes.A4)
|
||||||
|
.ProduceImages()
|
||||||
|
.ShowResults()
|
||||||
|
.EnableDebugging()
|
||||||
|
.Render(ContentDirectionTemplate(Content));
|
||||||
|
|
||||||
|
void Content(IContainer container)
|
||||||
|
{
|
||||||
|
container
|
||||||
|
.Width(100)
|
||||||
|
.Height(100)
|
||||||
|
.Background(Colors.Grey.Lighten3)
|
||||||
|
.AlignCenter()
|
||||||
|
.AlignMiddle()
|
||||||
|
|
||||||
|
.Unconstrained()
|
||||||
|
|
||||||
|
.Width(50)
|
||||||
|
.Height(50)
|
||||||
|
.Background(Colors.Red.Lighten1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Inlined()
|
||||||
|
{
|
||||||
|
RenderingTest
|
||||||
|
.Create()
|
||||||
|
.PageSize(PageSizes.A4)
|
||||||
|
.ProduceImages()
|
||||||
|
.ShowResults()
|
||||||
|
.EnableDebugging()
|
||||||
|
.Render(ContentDirectionTemplate(Content));
|
||||||
|
|
||||||
|
void Content(IContainer container)
|
||||||
|
{
|
||||||
|
container.Column(column =>
|
||||||
|
{
|
||||||
|
column.Spacing(10);
|
||||||
|
|
||||||
|
column.Item().Text("Default alignment").FontSize(14).SemiBold();
|
||||||
|
column.Item().Element(ContentWithAlignment(null));
|
||||||
|
|
||||||
|
column.Item().Text("Left alignment").FontSize(14).SemiBold();
|
||||||
|
column.Item().Element(ContentWithAlignment(InlinedAlignment.Left));
|
||||||
|
|
||||||
|
column.Item().Text("Center alignment").FontSize(14).SemiBold();
|
||||||
|
column.Item().Element(ContentWithAlignment(InlinedAlignment.Center));
|
||||||
|
|
||||||
|
column.Item().Text("Right alignment").FontSize(14).SemiBold();
|
||||||
|
column.Item().Element(ContentWithAlignment(InlinedAlignment.Right));
|
||||||
|
});
|
||||||
|
|
||||||
|
static Action<IContainer> ContentWithAlignment(InlinedAlignment? alignment)
|
||||||
|
{
|
||||||
|
return container =>
|
||||||
|
{
|
||||||
|
container.Inlined(inlined =>
|
||||||
|
{
|
||||||
|
inlined.Spacing(5);
|
||||||
|
|
||||||
|
inlined.Alignment(alignment);
|
||||||
|
|
||||||
|
inlined.Item().Height(50).Width(50).Background(Colors.Red.Lighten1);
|
||||||
|
inlined.Item().Height(50).Width(75).Background(Colors.Green.Lighten1);
|
||||||
|
inlined.Item().Height(50).Width(100).Background(Colors.Blue.Lighten1);
|
||||||
|
inlined.Item().Height(50).Width(125).Background(Colors.Orange.Lighten1);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Text()
|
||||||
|
{
|
||||||
|
RenderingTest
|
||||||
|
.Create()
|
||||||
|
.PageSize(PageSizes.A4)
|
||||||
|
.ProduceImages()
|
||||||
|
.ShowResults()
|
||||||
|
.EnableDebugging()
|
||||||
|
.Render(ContentDirectionTemplate(Content));
|
||||||
|
|
||||||
|
void Content(IContainer container)
|
||||||
|
{
|
||||||
|
container.Column(column =>
|
||||||
|
{
|
||||||
|
column.Spacing(10);
|
||||||
|
|
||||||
|
column.Item().Text("Default alignment").FontSize(14).SemiBold();
|
||||||
|
column.Item().Element(ContentWithAlignment(null));
|
||||||
|
|
||||||
|
column.Item().Text("Left alignment").FontSize(14).SemiBold();
|
||||||
|
column.Item().Element(ContentWithAlignment(HorizontalAlignment.Left));
|
||||||
|
|
||||||
|
column.Item().Text("Center alignment").FontSize(14).SemiBold();
|
||||||
|
column.Item().Element(ContentWithAlignment(HorizontalAlignment.Center));
|
||||||
|
|
||||||
|
column.Item().Text("Right alignment").FontSize(14).SemiBold();
|
||||||
|
column.Item().Element(ContentWithAlignment(HorizontalAlignment.Right));
|
||||||
|
});
|
||||||
|
|
||||||
|
static Action<IContainer> ContentWithAlignment(HorizontalAlignment? alignment)
|
||||||
|
{
|
||||||
|
return container =>
|
||||||
|
{
|
||||||
|
container.Text(text =>
|
||||||
|
{
|
||||||
|
text.Alignment = alignment; // internal API
|
||||||
|
|
||||||
|
text.Span("Lorem ipsum").Bold().FontColor(Colors.Red.Medium);
|
||||||
|
text.Element().Width(5);
|
||||||
|
text.Span(Placeholders.LoremIpsum());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Decoration()
|
||||||
|
{
|
||||||
|
RenderingTest
|
||||||
|
.Create()
|
||||||
|
.PageSize(PageSizes.A4)
|
||||||
|
.ProduceImages()
|
||||||
|
.ShowResults()
|
||||||
|
.EnableDebugging()
|
||||||
|
.Render(ContentDirectionTemplate(Content));
|
||||||
|
|
||||||
|
void Content(IContainer container)
|
||||||
|
{
|
||||||
|
container.Decoration(decoration =>
|
||||||
|
{
|
||||||
|
decoration.Before().Background(Colors.Green.Lighten1).Padding(5).Text("Before").FontSize(16);
|
||||||
|
decoration.Content().Background(Colors.Green.Lighten2).Padding(5).Text("Content").FontSize(16);
|
||||||
|
decoration.After().Background(Colors.Green.Lighten3).Padding(5).Text("After").FontSize(16);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Dynamic()
|
||||||
|
{
|
||||||
|
RenderingTest
|
||||||
|
.Create()
|
||||||
|
.PageSize(PageSizes.A4)
|
||||||
|
.ProduceImages()
|
||||||
|
.ShowResults()
|
||||||
|
.EnableDebugging()
|
||||||
|
.Render(ContentDirectionTemplate(Content));
|
||||||
|
|
||||||
|
void Content(IContainer container)
|
||||||
|
{
|
||||||
|
container.Dynamic(new SimpleDynamic());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SimpleDynamic : IDynamicComponent<int>
|
||||||
|
{
|
||||||
|
public int State { get; set; }
|
||||||
|
|
||||||
|
public DynamicComponentComposeResult Compose(DynamicContext context)
|
||||||
|
{
|
||||||
|
var content = context.CreateElement(container =>
|
||||||
|
{
|
||||||
|
container.Row(row =>
|
||||||
|
{
|
||||||
|
row.ConstantItem(50).Background(Colors.Red.Lighten2).Height(50);
|
||||||
|
row.ConstantItem(75).Background(Colors.Green.Lighten2).Height(50);
|
||||||
|
row.ConstantItem(100).Background(Colors.Blue.Lighten2).Height(50);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return new DynamicComponentComposeResult
|
||||||
|
{
|
||||||
|
Content = content,
|
||||||
|
HasMoreContent = false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using QuestPDF.Examples.Engine;
|
using QuestPDF.Examples.Engine;
|
||||||
@@ -43,7 +44,7 @@ namespace QuestPDF.Examples
|
|||||||
.Height(50)
|
.Height(50)
|
||||||
.AlignCenter()
|
.AlignCenter()
|
||||||
.AlignMiddle()
|
.AlignMiddle()
|
||||||
.Text(i)
|
.Text(i.ToString(CultureInfo.InvariantCulture))
|
||||||
.FontSize(16 + i / 4);
|
.FontSize(16 + i / 4);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using QuestPDF.Elements;
|
using QuestPDF.Elements;
|
||||||
@@ -126,9 +127,9 @@ namespace QuestPDF.Examples
|
|||||||
.Padding(5)
|
.Padding(5)
|
||||||
.Row(row =>
|
.Row(row =>
|
||||||
{
|
{
|
||||||
row.ConstantItem(30).Text(index + 1);
|
row.ConstantItem(30).Text((index + 1).ToString(CultureInfo.InvariantCulture));
|
||||||
row.RelativeItem().Text(item.ItemName);
|
row.RelativeItem().Text(item.ItemName);
|
||||||
row.ConstantItem(50).AlignRight().Text(item.Count);
|
row.ConstantItem(50).AlignRight().Text(item.Count.ToString(CultureInfo.InvariantCulture));
|
||||||
row.ConstantItem(50).AlignRight().Text($"{item.Price}$");
|
row.ConstantItem(50).AlignRight().Text($"{item.Price}$");
|
||||||
row.ConstantItem(50).AlignRight().Text($"{item.Count*item.Price}$");
|
row.ConstantItem(50).AlignRight().Text($"{item.Count*item.Price}$");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using QuestPDF.Elements;
|
using QuestPDF.Elements;
|
||||||
@@ -96,9 +97,9 @@ namespace QuestPDF.Examples
|
|||||||
{
|
{
|
||||||
var item = Items[index];
|
var item = Items[index];
|
||||||
|
|
||||||
table.Cell().Element(Style).Text(index + 1);
|
table.Cell().Element(Style).Text((index + 1).ToString(CultureInfo.InvariantCulture));
|
||||||
table.Cell().Element(Style).Text(item.ItemName);
|
table.Cell().Element(Style).Text(item.ItemName);
|
||||||
table.Cell().Element(Style).AlignRight().Text(item.Count);
|
table.Cell().Element(Style).AlignRight().Text(item.Count.ToString(CultureInfo.InvariantCulture));
|
||||||
table.Cell().Element(Style).AlignRight().Text($"{item.Price}$");
|
table.Cell().Element(Style).AlignRight().Text($"{item.Price}$");
|
||||||
table.Cell().Element(Style).AlignRight().Text($"{item.Count*item.Price}$");
|
table.Cell().Element(Style).AlignRight().Text($"{item.Count*item.Price}$");
|
||||||
|
|
||||||
|
|||||||
@@ -65,9 +65,9 @@ namespace QuestPDF.Examples.Engine
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RenderingTest ShowResults(bool value = true)
|
public RenderingTest ShowResults()
|
||||||
{
|
{
|
||||||
ShowResult = value;
|
ShowResult = true;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,10 +9,10 @@
|
|||||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.2" />
|
<PackageReference Include="BenchmarkDotNet" Version="0.13.2" />
|
||||||
<PackageReference Include="microcharts" Version="0.9.5.9" />
|
<PackageReference Include="microcharts" Version="0.9.5.9" />
|
||||||
<PackageReference Include="nunit" Version="3.13.3" />
|
<PackageReference Include="nunit" Version="3.13.3" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
|
||||||
<PackageReference Include="SkiaSharp" Version="2.80.4" />
|
<PackageReference Include="SkiaSharp" Version="2.88.3" />
|
||||||
<PackageReference Include="Svg.Skia" Version="0.5.10" />
|
<PackageReference Include="Svg.Skia" Version="0.5.18" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -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,13 +1,8 @@
|
|||||||
using System;
|
using System.Linq;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using QuestPDF.Drawing;
|
|
||||||
using QuestPDF.Elements;
|
|
||||||
using QuestPDF.Examples.Engine;
|
using QuestPDF.Examples.Engine;
|
||||||
using QuestPDF.Fluent;
|
using QuestPDF.Fluent;
|
||||||
using QuestPDF.Helpers;
|
using QuestPDF.Helpers;
|
||||||
using QuestPDF.Infrastructure;
|
|
||||||
|
|
||||||
namespace QuestPDF.Examples
|
namespace QuestPDF.Examples
|
||||||
{
|
{
|
||||||
@@ -16,77 +11,36 @@ namespace QuestPDF.Examples
|
|||||||
[Test]
|
[Test]
|
||||||
public void Benchmark()
|
public void Benchmark()
|
||||||
{
|
{
|
||||||
GenerateAndCollect();
|
RenderingTest
|
||||||
|
.Create()
|
||||||
var stopwatch = new Stopwatch();
|
.ProducePdf()
|
||||||
stopwatch.Start();
|
.PageSize(PageSizes.A4)
|
||||||
|
.ShowResults()
|
||||||
foreach (var _ in Enumerable.Range(0, 10000))
|
.MaxPages(10_000)
|
||||||
{
|
.EnableCaching(true)
|
||||||
GenerateAndCollect();
|
.EnableDebugging(false)
|
||||||
}
|
.Render(container =>
|
||||||
|
{
|
||||||
stopwatch.Stop();
|
container
|
||||||
|
.Padding(10)
|
||||||
Console.WriteLine($"Execution time: {stopwatch.Elapsed:g}");
|
.MinimalBox()
|
||||||
|
.Border(1)
|
||||||
void GenerateAndCollect()
|
.Table(table =>
|
||||||
{
|
|
||||||
var container = new Container();
|
|
||||||
|
|
||||||
container
|
|
||||||
.Padding(10)
|
|
||||||
.MinimalBox()
|
|
||||||
.Border(1)
|
|
||||||
.Column(column =>
|
|
||||||
{
|
|
||||||
const int numberOfRows = 100;
|
|
||||||
const int numberOfColumns = 10;
|
|
||||||
|
|
||||||
for (var y = 0; y < numberOfRows; y++)
|
|
||||||
{
|
{
|
||||||
column.Item().Row(row =>
|
const int numberOfRows = 100_000;
|
||||||
|
const int numberOfColumns = 10;
|
||||||
|
|
||||||
|
table.ColumnsDefinition(columns =>
|
||||||
{
|
{
|
||||||
for (var x = 0; x < numberOfColumns; x++)
|
foreach (var _ in Enumerable.Range(0, numberOfColumns))
|
||||||
{
|
columns.RelativeColumn();
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ElementCacheManager.Collect(container);
|
foreach (var row in Enumerable.Range(0, numberOfRows))
|
||||||
}
|
foreach (var column in Enumerable.Range(0, numberOfColumns))
|
||||||
|
table.Cell().Background(Placeholders.BackgroundColor()).Padding(5).Text($"{row}_{column}");
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using QuestPDF.Drawing;
|
using QuestPDF.Drawing;
|
||||||
@@ -389,12 +390,12 @@ namespace QuestPDF.Examples
|
|||||||
table.Cell().Element(CellStyle).ExtendHorizontal().AlignLeft().Text(page.name);
|
table.Cell().Element(CellStyle).ExtendHorizontal().AlignLeft().Text(page.name);
|
||||||
|
|
||||||
// inches
|
// inches
|
||||||
table.Cell().Element(CellStyle).Text(page.width);
|
table.Cell().Element(CellStyle).Text(page.width.ToString(CultureInfo.InvariantCulture));
|
||||||
table.Cell().Element(CellStyle).Text(page.height);
|
table.Cell().Element(CellStyle).Text(page.height.ToString(CultureInfo.InvariantCulture));
|
||||||
|
|
||||||
// points
|
// points
|
||||||
table.Cell().Element(CellStyle).Text(page.width * inchesToPoints);
|
table.Cell().Element(CellStyle).Text((page.width * inchesToPoints).ToString(CultureInfo.InvariantCulture));
|
||||||
table.Cell().Element(CellStyle).Text(page.height * inchesToPoints);
|
table.Cell().Element(CellStyle).Text((page.height * inchesToPoints).ToString(CultureInfo.InvariantCulture));
|
||||||
|
|
||||||
IContainer CellStyle(IContainer container) => DefaultCellStyle(container, Colors.White);
|
IContainer CellStyle(IContainer container) => DefaultCellStyle(container, Colors.White);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ namespace QuestPDF.Examples
|
|||||||
{
|
{
|
||||||
page.Margin(50);
|
page.Margin(50);
|
||||||
|
|
||||||
page.Content().Column(column =>
|
page.Content().PaddingVertical(10).Column(column =>
|
||||||
{
|
{
|
||||||
column.Item().Element(Title);
|
column.Item().Element(Title);
|
||||||
column.Item().PageBreak();
|
column.Item().PageBreak();
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using QuestPDF.Elements.Text;
|
||||||
using QuestPDF.Examples.Engine;
|
using QuestPDF.Examples.Engine;
|
||||||
using QuestPDF.Fluent;
|
using QuestPDF.Fluent;
|
||||||
using QuestPDF.Helpers;
|
using QuestPDF.Helpers;
|
||||||
@@ -121,7 +122,132 @@ namespace QuestPDF.Examples
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void LetterSpacing()
|
||||||
|
{
|
||||||
|
RenderingTest
|
||||||
|
.Create()
|
||||||
|
.PageSize(500, 700)
|
||||||
|
.ProduceImages()
|
||||||
|
.ShowResults()
|
||||||
|
.Render(container =>
|
||||||
|
{
|
||||||
|
container
|
||||||
|
.Padding(20)
|
||||||
|
.Column(column =>
|
||||||
|
{
|
||||||
|
var letterSpacing = new[] { -0.1f, 0f, 0.2f };
|
||||||
|
var paragraph = Placeholders.Sentence().ToUpper();
|
||||||
|
|
||||||
|
foreach (var spacing in letterSpacing)
|
||||||
|
{
|
||||||
|
column
|
||||||
|
.Item()
|
||||||
|
.Border(1)
|
||||||
|
.Padding(10)
|
||||||
|
.Column(nestedColumn =>
|
||||||
|
{
|
||||||
|
nestedColumn.Item()
|
||||||
|
.Text(paragraph)
|
||||||
|
.FontSize(16)
|
||||||
|
.LetterSpacing(spacing);
|
||||||
|
|
||||||
|
nestedColumn.Item()
|
||||||
|
.Text($"Letter spacing of {spacing} em")
|
||||||
|
.FontSize(10)
|
||||||
|
.Italic()
|
||||||
|
.FontColor(Colors.Blue.Medium);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void LetterSpacing_Arabic()
|
||||||
|
{
|
||||||
|
RenderingTest
|
||||||
|
.Create()
|
||||||
|
.PageSize(500, 700)
|
||||||
|
.ProduceImages()
|
||||||
|
.ShowResults()
|
||||||
|
.Render(container =>
|
||||||
|
{
|
||||||
|
container
|
||||||
|
.Padding(50)
|
||||||
|
.Column(column =>
|
||||||
|
{
|
||||||
|
var letterSpacing = new[] { -0.1f, 0f, 0.2f };
|
||||||
|
var paragraph = "ينا الألم. في بعض الأحيان ونظراً للالتزامات التي يفرضها علينا";
|
||||||
|
foreach (var spacing in letterSpacing)
|
||||||
|
{
|
||||||
|
column
|
||||||
|
.Item()
|
||||||
|
.Border(1)
|
||||||
|
.Padding(10)
|
||||||
|
.Column(nestedColumn =>
|
||||||
|
{
|
||||||
|
nestedColumn.Item()
|
||||||
|
.Text(paragraph)
|
||||||
|
.FontSize(16)
|
||||||
|
.FontFamily(Fonts.Calibri)
|
||||||
|
.LetterSpacing(spacing);
|
||||||
|
|
||||||
|
nestedColumn.Item()
|
||||||
|
.Text($"Letter spacing of {spacing} em")
|
||||||
|
.FontSize(10)
|
||||||
|
.Italic()
|
||||||
|
.FontColor(Colors.Blue.Medium);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void LetterSpacing_Unicode()
|
||||||
|
{
|
||||||
|
RenderingTest
|
||||||
|
.Create()
|
||||||
|
.PageSize(500, 700)
|
||||||
|
.ProduceImages()
|
||||||
|
.ShowResults()
|
||||||
|
.Render(container =>
|
||||||
|
{
|
||||||
|
container
|
||||||
|
.Padding(50)
|
||||||
|
.Column(column =>
|
||||||
|
{
|
||||||
|
var letterSpacing = new[] { 0f, 0.5f };
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var paragraph = "Ţ̴̡̧̤̮̺̤̗͎̱̹͙͎͖͂̿̓́̉̊̀̍͜h̵̞̘͇̾̎̏̅į̵̹̖͔͉̰̎̉̄̐̏͑͂̅̃̃͘͝s̷͓͉̭̭̯̬̥̻̰̩̦̑̀̀͌́̒̍̒̌̇͛̀͛́̎ ̷̡̡̟͕̳̺̝̼͇͔̬̟̖͍̈́̽͜͝͝i̶͔͚̟̊̐͛́͛̄̌ṡ̸̡̤̪͙͍̥͙̟̼̝̰̥͈̿̓̄̿̓͠ ̶̢̦̙͍̯̖̱̰̯͕͔͎̯̝̎͑t̸͖̲̱̼̎͐̎̉̾̎̾̌̅̔̏͘ȩ̶̝̫̙͓̙̣̔̀̌̔̋̂̑̈́̏̀̈͘̕͜͝s̸̫̝̮̻̼͐̅̄̎̎̑͝ț̷̨̢̨̻͈̮̞̆͗̓͊̃̌͂̑̉̕̕͜͝͝";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
foreach (var spacing in letterSpacing)
|
||||||
|
{
|
||||||
|
column.Item()
|
||||||
|
.Text($"Letter spacing of {spacing} em")
|
||||||
|
.FontSize(10)
|
||||||
|
.Italic()
|
||||||
|
.FontColor(Colors.Blue.Medium);
|
||||||
|
|
||||||
|
column.Item()
|
||||||
|
.PaddingVertical(50)
|
||||||
|
.Text(paragraph)
|
||||||
|
.FontSize(16)
|
||||||
|
.FontFamily(Fonts.Calibri)
|
||||||
|
.LetterSpacing(spacing);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void SuperscriptSubscript_Simple()
|
public void SuperscriptSubscript_Simple()
|
||||||
{
|
{
|
||||||
@@ -522,7 +648,7 @@ namespace QuestPDF.Examples
|
|||||||
|
|
||||||
page.Content().Column(column =>
|
page.Content().Column(column =>
|
||||||
{
|
{
|
||||||
column.Item().Text(null);
|
column.Item().Text((string) null);
|
||||||
|
|
||||||
column.Item().Text(text =>
|
column.Item().Text(text =>
|
||||||
{
|
{
|
||||||
@@ -554,7 +680,7 @@ namespace QuestPDF.Examples
|
|||||||
|
|
||||||
page.Content().Column(column =>
|
page.Content().Column(column =>
|
||||||
{
|
{
|
||||||
column.Item().Text(null);
|
column.Item().Text((string) null);
|
||||||
|
|
||||||
column.Item().Text(text =>
|
column.Item().Text(text =>
|
||||||
{
|
{
|
||||||
@@ -603,7 +729,7 @@ namespace QuestPDF.Examples
|
|||||||
{
|
{
|
||||||
RenderingTest
|
RenderingTest
|
||||||
.Create()
|
.Create()
|
||||||
.PageSize(500, 100)
|
.PageSize(250, 100)
|
||||||
|
|
||||||
.ProduceImages()
|
.ProduceImages()
|
||||||
.ShowResults()
|
.ShowResults()
|
||||||
@@ -613,9 +739,176 @@ 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void FontFallback()
|
||||||
|
{
|
||||||
|
RenderingTest
|
||||||
|
.Create()
|
||||||
|
.ProduceImages()
|
||||||
|
.ShowResults()
|
||||||
|
.RenderDocument(container =>
|
||||||
|
{
|
||||||
|
container.Page(page =>
|
||||||
|
{
|
||||||
|
page.Margin(50);
|
||||||
|
page.PageColor(Colors.White);
|
||||||
|
page.DefaultTextStyle(x => x
|
||||||
|
.Fallback(y => y.FontFamily("Segoe UI Emoji")
|
||||||
|
.Fallback(y => y.FontFamily("Microsoft YaHei"))));
|
||||||
|
|
||||||
|
page.Size(PageSizes.A4);
|
||||||
|
|
||||||
|
page.Content().Text(t =>
|
||||||
|
{
|
||||||
|
t.Line("This is normal text.");
|
||||||
|
t.EmptyLine();
|
||||||
|
|
||||||
|
t.Line("Following line should use font fallback:");
|
||||||
|
t.Line("中文文本");
|
||||||
|
t.EmptyLine();
|
||||||
|
|
||||||
|
t.Line("The following line contains a mix of known and unknown characters.");
|
||||||
|
t.Line("Mixed line: This 中文 is 文文 a mixed 本 本 line 本 中文文本!");
|
||||||
|
t.EmptyLine();
|
||||||
|
|
||||||
|
t.Line("Emojis work out of the box because of font fallback: 😊😅🥳👍❤😍👌");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void WordWrappingStability()
|
||||||
|
{
|
||||||
|
// instruction: check if any characters repeat when performing the word-wrapping algorithm
|
||||||
|
|
||||||
|
RenderingTest
|
||||||
|
.Create()
|
||||||
|
.PageSize(PageSizes.A4)
|
||||||
|
.ProducePdf()
|
||||||
|
.ShowResults()
|
||||||
|
.Render(container =>
|
||||||
|
{
|
||||||
|
var text = "Lorem ipsum dolor sit amet consectetuer";
|
||||||
|
|
||||||
|
container
|
||||||
|
.Padding(20)
|
||||||
|
.Column(column =>
|
||||||
|
{
|
||||||
|
column.Spacing(10);
|
||||||
|
|
||||||
|
foreach (var width in Enumerable.Range(25, 200))
|
||||||
|
{
|
||||||
|
column
|
||||||
|
.Item()
|
||||||
|
.MaxWidth(width)
|
||||||
|
.Background(Colors.Grey.Lighten3)
|
||||||
|
.Text(text);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void AdvancedLanguagesSupport()
|
||||||
|
{
|
||||||
|
RenderingTest
|
||||||
|
.Create()
|
||||||
|
.PageSize(new PageSize(400, 400))
|
||||||
|
.ProduceImages()
|
||||||
|
.ShowResults()
|
||||||
|
.Render(container =>
|
||||||
|
{
|
||||||
|
var text = "في المعلوماتية أو الرياضيات، خوارزمية الترتيب هي خوارزمية تمكن من تنظيم مجموعة عناصر حسب ترتيب محدد.";
|
||||||
|
|
||||||
|
container
|
||||||
|
.Padding(20)
|
||||||
|
.ContentFromRightToLeft()
|
||||||
|
.Text(text)
|
||||||
|
.FontFamily(Fonts.Calibri)
|
||||||
|
.FontSize(22);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void WordWrappingWhenRightToLeft()
|
||||||
|
{
|
||||||
|
RenderingTest
|
||||||
|
.Create()
|
||||||
|
.PageSize(new PageSize(1000, 500))
|
||||||
|
.ProducePdf()
|
||||||
|
.ShowResults()
|
||||||
|
.Render(container =>
|
||||||
|
{
|
||||||
|
var text = "في المعلوماتية أو الرياضيات، خوارزمية الترتيب هي خوارزمية تمكن من تنظيم مجموعة عناصر حسب ترتيب محدد.";
|
||||||
|
|
||||||
|
container
|
||||||
|
.Padding(25)
|
||||||
|
.ContentFromRightToLeft()
|
||||||
|
.Column(column =>
|
||||||
|
{
|
||||||
|
column.Spacing(20);
|
||||||
|
|
||||||
|
foreach (var size in new[] { 36, 34, 32, 30, 15 })
|
||||||
|
{
|
||||||
|
column
|
||||||
|
.Item()
|
||||||
|
.ShowEntire()
|
||||||
|
.MaxWidth(size * 25)
|
||||||
|
.Background(Colors.Grey.Lighten3)
|
||||||
|
.MinimalBox()
|
||||||
|
.Background(Colors.Grey.Lighten2)
|
||||||
|
.Text(text)
|
||||||
|
.FontSize(20)
|
||||||
|
.FontFamily("Segoe UI");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ForcingTextDirection()
|
||||||
|
{
|
||||||
|
RenderingTest
|
||||||
|
.Create()
|
||||||
|
.PageSize(new PageSize(1000, 500))
|
||||||
|
.ProduceImages()
|
||||||
|
.ShowResults()
|
||||||
|
.Render(container =>
|
||||||
|
{
|
||||||
|
container
|
||||||
|
.Padding(10)
|
||||||
|
.DefaultTextStyle(x => x.FontSize(24).FontFamily("Calibri"))
|
||||||
|
.Column(column =>
|
||||||
|
{
|
||||||
|
column.Spacing(10);
|
||||||
|
|
||||||
|
var word = "الجوريتم";
|
||||||
|
var definition = "algorithm in Arabic";
|
||||||
|
|
||||||
|
var text = $"{word} - {definition}";
|
||||||
|
|
||||||
|
// text direction is automatically detected using the first word
|
||||||
|
column.Item().Text(text);
|
||||||
|
|
||||||
|
// it is possible to force specific content direction
|
||||||
|
column.Item().Text(text).DirectionFromLeftToRight();
|
||||||
|
column.Item().Text(text).DirectionFromRightToLeft();
|
||||||
|
|
||||||
|
// to combine text in various content directions, split it into segments
|
||||||
|
column.Item().Text(text =>
|
||||||
|
{
|
||||||
|
text.Span(word);
|
||||||
|
text.Span(" - ");
|
||||||
|
text.Span(definition);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.8.0</Version>
|
<Version>2022.12.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>
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||||
<PackageIcon>Logo.png</PackageIcon>
|
<PackageIcon>Logo.png</PackageIcon>
|
||||||
<PackageIconUrl>https://www.questpdf.com/images/package-logo.png</PackageIconUrl>
|
<PackageIconUrl>https://www.questpdf.com/images/package-logo.png</PackageIconUrl>
|
||||||
|
<PackageReadmeFile>PackageReadme.md</PackageReadmeFile>
|
||||||
<PackageProjectUrl>https://www.questpdf.com/</PackageProjectUrl>
|
<PackageProjectUrl>https://www.questpdf.com/</PackageProjectUrl>
|
||||||
<RepositoryUrl>https://github.com/QuestPDF/library.git</RepositoryUrl>
|
<RepositoryUrl>https://github.com/QuestPDF/library.git</RepositoryUrl>
|
||||||
<RepositoryType>git</RepositoryType>
|
<RepositoryType>git</RepositoryType>
|
||||||
@@ -33,23 +34,22 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="Resources\Logo.png" />
|
<EmbeddedResource Include="Resources\Logo.png" />
|
||||||
<None Include="Resources\Logo.png">
|
<None Include="Resources\Logo.png" Pack="true" PackagePath="\" />
|
||||||
<Pack>true</Pack>
|
<None Include="Resources\PackageReadme.md" Pack="true" PackagePath="\" />
|
||||||
<Visible>false</Visible>
|
|
||||||
<PackagePath>\</PackagePath>
|
|
||||||
</None>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||||
<PackageReference Include="Avalonia" Version="0.10.10" />
|
<PackageReference Include="Avalonia" Version="0.10.18" />
|
||||||
<PackageReference Include="Avalonia.Desktop" Version="0.10.10" />
|
<PackageReference Include="Avalonia.Desktop" Version="0.10.18" />
|
||||||
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.10" />
|
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.18" />
|
||||||
<PackageReference Include="Avalonia.Markup.Xaml.Loader" Version="0.10.10" />
|
<PackageReference Include="Avalonia.Markup.Xaml.Loader" Version="0.10.18" />
|
||||||
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.10" />
|
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.18" />
|
||||||
<PackageReference Include="ReactiveUI" Version="17.1.50" />
|
<PackageReference Include="ReactiveUI" Version="18.4.1" />
|
||||||
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="2.80.4" />
|
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="2.88.3" />
|
||||||
|
<PackageReference Include="SkiaSharp.NativeAssets.Win32" Version="2.88.3" />
|
||||||
|
<PackageReference Include="SkiaSharp.NativeAssets.macOS" Version="2.88.3" />
|
||||||
<PackageReference Include="System.Reactive" Version="5.0.0" />
|
<PackageReference Include="System.Reactive" Version="5.0.0" />
|
||||||
<PackageReference Include="SkiaSharp" Version="2.80.4" />
|
<PackageReference Include="SkiaSharp" Version="2.88.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
[](https://www.nuget.org/packages/QuestPDF/)
|
||||||
|
[](https://github.com/QuestPDF/QuestPDF/stargazers)
|
||||||
|
[](https://www.nuget.org/packages/QuestPDF/)
|
||||||
|
[](https://www.nuget.org/packages/QuestPDF/)
|
||||||
|
[](https://github.com/QuestPDF/QuestPDF/blob/main/LICENSE)
|
||||||
|
[](https://github.com/sponsors/QuestPDF)
|
||||||
|
|
||||||
|
QuestPDF is an open-source .NET library for PDF documents generation.
|
||||||
|
|
||||||
|
It offers a layout engine designed with a full paging support in mind. The document consists of many simple elements (e.g. border, background, image, text, padding, table, grid etc.) that are composed together to create more complex structures. This way, as a developer, you can understand the behavior of every element and use them with full confidence. Additionally, the document and all its elements support paging functionality. For example, an element can be moved to the next page (if there is not enough space) or even be split between pages like table's rows.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
[](https://www.questpdf.com/getting-started.html)
|
||||||
|
A short and easy to follow tutorial showing how to design an invoice document under 200 lines of code.
|
||||||
|
|
||||||
|
|
||||||
|
[](https://www.questpdf.com/api-reference/index.html)
|
||||||
|
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)
|
||||||
|
Everything that may help you designing great reports and create reusable code that is easy to maintain.
|
||||||
|
|
||||||
|
## Simplicity is the key
|
||||||
|
|
||||||
|
How easy it is to start and prototype with QuestPDF? Really easy thanks to its minimal API! Please analyse the code below:
|
||||||
|
|
||||||
|
```#
|
||||||
|
using QuestPDF.Fluent;
|
||||||
|
using QuestPDF.Helpers;
|
||||||
|
using QuestPDF.Infrastructure;
|
||||||
|
|
||||||
|
// code in your main method
|
||||||
|
Document.Create(container =>
|
||||||
|
{
|
||||||
|
container.Page(page =>
|
||||||
|
{
|
||||||
|
page.Size(PageSizes.A4);
|
||||||
|
page.Margin(2, Unit.Centimetre);
|
||||||
|
page.Background(Colors.White);
|
||||||
|
page.DefaultTextStyle(x => x.FontSize(20));
|
||||||
|
|
||||||
|
page.Header()
|
||||||
|
.Text("Hello PDF!")
|
||||||
|
.SemiBold().FontSize(36).FontColor(Colors.Blue.Medium);
|
||||||
|
|
||||||
|
page.Content()
|
||||||
|
.PaddingVertical(1, Unit.Centimetre)
|
||||||
|
.Column(x =>
|
||||||
|
{
|
||||||
|
x.Spacing(20);
|
||||||
|
|
||||||
|
x.Item().Text(Placeholders.LoremIpsum());
|
||||||
|
x.Item().Image(Placeholders.Image(200, 100));
|
||||||
|
});
|
||||||
|
|
||||||
|
page.Footer()
|
||||||
|
.AlignCenter()
|
||||||
|
.Text(x =>
|
||||||
|
{
|
||||||
|
x.Span("Page ");
|
||||||
|
x.CurrentPageNumber();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.GeneratePdf("hello.pdf");
|
||||||
|
```
|
||||||
|
|
||||||
|
And compare it to the produced PDF file:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Are you ready for more?
|
||||||
|
|
||||||
|
The Fluent API of QuestPDF scales really well. It is easy to create and maintain even most complex documents. Read [the Getting started tutorial](https://www.questpdf.com/documentation/getting-started.html) to learn QuestPDF basics and implement an invoice under 200 lines of code. You can also investigate and play with the code from [the example repository](https://github.com/QuestPDF/example-invoice).
|
||||||
|
|
||||||
|

|
||||||
@@ -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})";
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,9 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
|
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
|
||||||
<PackageReference Include="nunit" Version="3.13.2" />
|
<PackageReference Include="nunit" Version="3.13.2" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
|
||||||
<PackageReference Include="SkiaSharp" Version="2.80.4" />
|
<PackageReference Include="SkiaSharp" Version="2.88.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -86,5 +86,43 @@ namespace QuestPDF.UnitTests
|
|||||||
.ExpectCanvasTranslate(new Position(-300, 0))
|
.ExpectCanvasTranslate(new Position(-300, 0))
|
||||||
.CheckDrawResult();
|
.CheckDrawResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Draw_HorizontalCenter_VerticalNone()
|
||||||
|
{
|
||||||
|
TestPlan
|
||||||
|
.For(x => new Alignment
|
||||||
|
{
|
||||||
|
Horizontal = HorizontalAlignment.Center,
|
||||||
|
Vertical = null,
|
||||||
|
|
||||||
|
Child = x.CreateChild()
|
||||||
|
})
|
||||||
|
.DrawElement(new Size(400, 300))
|
||||||
|
.ExpectChildMeasure(expectedInput: new Size(400, 300), returns: SpacePlan.FullRender(new Size(100, 50)))
|
||||||
|
.ExpectCanvasTranslate(new Position(150, 0))
|
||||||
|
.ExpectChildDraw(new Size(100, 300))
|
||||||
|
.ExpectCanvasTranslate(new Position(-150, 0))
|
||||||
|
.CheckDrawResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Draw_HorizontalNone_VerticalMiddle()
|
||||||
|
{
|
||||||
|
TestPlan
|
||||||
|
.For(x => new Alignment
|
||||||
|
{
|
||||||
|
Horizontal = null,
|
||||||
|
Vertical = VerticalAlignment.Middle,
|
||||||
|
|
||||||
|
Child = x.CreateChild()
|
||||||
|
})
|
||||||
|
.DrawElement(new Size(400, 300))
|
||||||
|
.ExpectChildMeasure(expectedInput: new Size(400, 300), returns: SpacePlan.FullRender(new Size(100, 50)))
|
||||||
|
.ExpectCanvasTranslate(new Position(0, 125))
|
||||||
|
.ExpectChildDraw(new Size(400, 50))
|
||||||
|
.ExpectCanvasTranslate(new Position(0, -125))
|
||||||
|
.CheckDrawResult();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,11 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="FluentAssertions" Version="6.7.0" />
|
<PackageReference Include="FluentAssertions" Version="6.8.0" />
|
||||||
<PackageReference Include="nunit" Version="3.13.2" />
|
<PackageReference Include="nunit" Version="3.13.2" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
|
||||||
<PackageReference Include="SkiaSharp" Version="2.80.4" />
|
<PackageReference Include="SkiaSharp" Version="2.88.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ namespace QuestPDF.Drawing
|
|||||||
document.Compose(container);
|
document.Compose(container);
|
||||||
var content = container.Compose();
|
var content = container.Compose();
|
||||||
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;
|
||||||
|
|
||||||
@@ -74,14 +75,12 @@ namespace QuestPDF.Drawing
|
|||||||
var pageContext = new PageContext();
|
var pageContext = new PageContext();
|
||||||
RenderPass(pageContext, new FreeCanvas(), content, debuggingState);
|
RenderPass(pageContext, new FreeCanvas(), content, debuggingState);
|
||||||
RenderPass(pageContext, canvas, content, debuggingState);
|
RenderPass(pageContext, canvas, content, debuggingState);
|
||||||
|
|
||||||
ElementCacheManager.Collect(content);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 =>
|
||||||
@@ -162,6 +173,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)
|
||||||
{
|
{
|
||||||
@@ -174,7 +203,7 @@ namespace QuestPDF.Drawing
|
|||||||
{
|
{
|
||||||
if (textBlockItem is TextBlockSpan textSpan)
|
if (textBlockItem is TextBlockSpan textSpan)
|
||||||
{
|
{
|
||||||
textSpan.Style = textSpan.Style.ApplyGlobalStyle(TextStyle.LibraryDefault);
|
textSpan.Style = textSpan.Style.ApplyGlobalStyle(documentDefaultTextStyle);
|
||||||
}
|
}
|
||||||
else if (textBlockItem is TextBlockElement textElement)
|
else if (textBlockItem is TextBlockElement textElement)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,76 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using QuestPDF.Infrastructure;
|
|
||||||
|
|
||||||
namespace QuestPDF.Drawing
|
|
||||||
{
|
|
||||||
internal class CircularBuffer
|
|
||||||
{
|
|
||||||
private const int BufferSize = 11_000;
|
|
||||||
|
|
||||||
private object[] Buffer = new object[BufferSize];
|
|
||||||
private int WriteIndex { get; set; } = 0;
|
|
||||||
private int ReadIndex { get; set; } = 0;
|
|
||||||
|
|
||||||
public T Get<T>() where T : class, new()
|
|
||||||
{
|
|
||||||
lock (this)
|
|
||||||
{
|
|
||||||
if (ReadIndex == WriteIndex)
|
|
||||||
return new T();
|
|
||||||
|
|
||||||
var index = ReadIndex;
|
|
||||||
ReadIndex = (ReadIndex + 1) % BufferSize;
|
|
||||||
|
|
||||||
var result = Buffer[index] as T;
|
|
||||||
Buffer[index] = null;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Store(object value)
|
|
||||||
{
|
|
||||||
lock (this)
|
|
||||||
{
|
|
||||||
Buffer[WriteIndex] = value;
|
|
||||||
WriteIndex = (WriteIndex + 1) % BufferSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// performance analysis:
|
|
||||||
// without: 115s
|
|
||||||
// ConcurrentQueue: 28s
|
|
||||||
// ConcurrentBag: 38s
|
|
||||||
// CircularBuffer: 30s
|
|
||||||
internal static class ElementCacheManager
|
|
||||||
{
|
|
||||||
private static ConcurrentDictionary<Type, ConcurrentQueue<object>> Cache { get; } = new();
|
|
||||||
|
|
||||||
public static T Get<T>() where T : class, new()
|
|
||||||
{
|
|
||||||
var buffer = Cache.GetOrAdd(typeof(T), _=> new ConcurrentQueue<object>());
|
|
||||||
return buffer.TryDequeue(out var result) ? result as T : new T();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Store<T>(T element)
|
|
||||||
{
|
|
||||||
var buffer = Cache.GetOrAdd(element.GetType(), _=> new ConcurrentQueue<object>());
|
|
||||||
buffer.Enqueue(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Collect(Element element)
|
|
||||||
{
|
|
||||||
foreach (var child in element.GetChildren())
|
|
||||||
Collect(child);
|
|
||||||
|
|
||||||
if (element is ICollectable collectable)
|
|
||||||
{
|
|
||||||
collectable.Collect();
|
|
||||||
Store(element);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,6 +4,11 @@ namespace QuestPDF.Drawing.Exceptions
|
|||||||
{
|
{
|
||||||
public class DocumentDrawingException : Exception
|
public class DocumentDrawingException : Exception
|
||||||
{
|
{
|
||||||
|
internal DocumentDrawingException(string message) : base(message)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
internal DocumentDrawingException(string message, Exception inner) : base(message, inner)
|
internal DocumentDrawingException(string message, Exception inner) : base(message, inner)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
namespace QuestPDF.Drawing
|
|
||||||
{
|
|
||||||
internal struct TextMeasurement
|
|
||||||
{
|
|
||||||
public int LineIndex { get; set; }
|
|
||||||
public float FragmentWidth { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using HarfBuzzSharp;
|
using HarfBuzzSharp;
|
||||||
|
using QuestPDF.Fluent;
|
||||||
using QuestPDF.Infrastructure;
|
using QuestPDF.Infrastructure;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
using Buffer = HarfBuzzSharp.Buffer;
|
using Buffer = HarfBuzzSharp.Buffer;
|
||||||
@@ -29,6 +30,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;
|
||||||
@@ -41,25 +48,34 @@ namespace QuestPDF.Drawing
|
|||||||
var xOffset = 0f;
|
var xOffset = 0f;
|
||||||
var yOffset = 0f;
|
var yOffset = 0f;
|
||||||
|
|
||||||
var glyphs = new ShapedGlyph[length];
|
// used for letter spacing calculation
|
||||||
|
var lastCluster = glyphInfos.LastOrDefault().Cluster;
|
||||||
|
var letterSpacing = (TextStyle.LetterSpacing ?? 0) * (TextStyle.Size ?? 16);
|
||||||
|
|
||||||
|
var glyphs = new ShapedGlyph[length];
|
||||||
|
|
||||||
for (var i = 0; i < length; i++)
|
for (var i = 0; i < length; i++)
|
||||||
{
|
{
|
||||||
|
// letter spacing should be applied between glyph clusters, not between individual glyphs,
|
||||||
|
// different cluster id indicates the end of the glyph cluster
|
||||||
|
if (lastCluster != glyphInfos[i].Cluster)
|
||||||
|
{
|
||||||
|
lastCluster = glyphInfos[i].Cluster;
|
||||||
|
xOffset += letterSpacing;
|
||||||
|
}
|
||||||
|
|
||||||
glyphs[i] = new ShapedGlyph
|
glyphs[i] = new ShapedGlyph
|
||||||
{
|
{
|
||||||
Codepoint = (ushort)glyphInfos[i].Codepoint,
|
Codepoint = (ushort)glyphInfos[i].Codepoint,
|
||||||
Position = new SKPoint(xOffset + glyphPositions[i].XOffset * scaleX, yOffset - glyphPositions[i].YOffset * scaleY),
|
Position = new SKPoint(xOffset + glyphPositions[i].XOffset * scaleX, yOffset - glyphPositions[i].YOffset * scaleY),
|
||||||
Width = glyphPositions[i].XAdvance * scaleX
|
Width = glyphPositions[i].XAdvance * scaleX
|
||||||
};
|
};
|
||||||
|
|
||||||
xOffset += glyphPositions[i].XAdvance * scaleX;
|
xOffset += glyphPositions[i].XAdvance * scaleX;
|
||||||
yOffset += glyphPositions[i].YAdvance * scaleY;
|
yOffset += glyphPositions[i].YAdvance * scaleY;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Settings.CheckIfAllTextGlyphsAreAvailableInSpecifiedFont)
|
|
||||||
CheckIfAllGlyphsAreAvailable(glyphs, text);
|
|
||||||
|
|
||||||
return new TextShapingResult(glyphs);
|
return new TextShapingResult(buffer.Direction, glyphs);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PopulateBufferWithText(Buffer buffer, string text)
|
void PopulateBufferWithText(Buffer buffer, string text)
|
||||||
@@ -78,20 +94,6 @@ namespace QuestPDF.Drawing
|
|||||||
else
|
else
|
||||||
throw new NotSupportedException("TextEncoding of type GlyphId is not supported.");
|
throw new NotSupportedException("TextEncoding of type GlyphId is not supported.");
|
||||||
}
|
}
|
||||||
|
|
||||||
void CheckIfAllGlyphsAreAvailable(ShapedGlyph[] glyphs, string originalText)
|
|
||||||
{
|
|
||||||
var containsMissingGlyphs = glyphs.Any(x => x.Codepoint == default);
|
|
||||||
|
|
||||||
if (!containsMissingGlyphs)
|
|
||||||
return;
|
|
||||||
|
|
||||||
throw new ArgumentException(
|
|
||||||
$"Detected missing font glyphs while rendering text. " +
|
|
||||||
$"This means that the document contains text with characters not present in the assigned font. " +
|
|
||||||
$"Such characters are replaced by placeholders, usually visible as empty rectangles. " +
|
|
||||||
$"Font family used: {TextStyle.FontFamily}. Issue detected in text: '{originalText}'");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal struct ShapedGlyph
|
internal struct ShapedGlyph
|
||||||
@@ -109,29 +111,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(),
|
||||||
if (glyph.Position.X + glyph.Width > maxWidth + Size.Epsilon)
|
_ => throw new ArgumentOutOfRangeException()
|
||||||
break;
|
};
|
||||||
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return index - 1;
|
int BreakTextLeftToRight()
|
||||||
|
{
|
||||||
|
var index = startIndex;
|
||||||
|
maxWidth += Glyphs[startIndex].Position.X;
|
||||||
|
|
||||||
|
while (index < Glyphs.Length)
|
||||||
|
{
|
||||||
|
var glyph = Glyphs[index];
|
||||||
|
|
||||||
|
if (glyph.Position.X + glyph.Width > maxWidth + Size.Epsilon)
|
||||||
|
break;
|
||||||
|
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return index - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int BreakTextRightToLeft()
|
||||||
|
{
|
||||||
|
var index = startIndex;
|
||||||
|
|
||||||
|
var startOffset = this[startIndex].Position.X + this[startIndex].Width;
|
||||||
|
|
||||||
|
while (index < Glyphs.Length)
|
||||||
|
{
|
||||||
|
if (startOffset - this[index].Position.X > maxWidth + Size.Epsilon)
|
||||||
|
break;
|
||||||
|
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return index - 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public float MeasureWidth(int startIndex, int endIndex)
|
public float MeasureWidth(int startIndex, int endIndex)
|
||||||
@@ -139,10 +177,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)
|
||||||
@@ -163,14 +206,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
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,19 +6,23 @@ namespace QuestPDF.Elements
|
|||||||
{
|
{
|
||||||
internal class Alignment : ContainerElement
|
internal class Alignment : ContainerElement
|
||||||
{
|
{
|
||||||
public VerticalAlignment Vertical { get; set; } = VerticalAlignment.Top;
|
public VerticalAlignment? Vertical { get; set; }
|
||||||
public HorizontalAlignment Horizontal { get; set; } = HorizontalAlignment.Left;
|
public HorizontalAlignment? Horizontal { get; set; }
|
||||||
|
|
||||||
internal override void Draw(Size availableSpace)
|
internal override void Draw(Size availableSpace)
|
||||||
{
|
{
|
||||||
if (Child == null)
|
if (Child == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var childSize = base.Measure(availableSpace);
|
var childMeasurement = base.Measure(availableSpace);
|
||||||
|
|
||||||
if (childSize.Type == SpacePlanType.Wrap)
|
if (childMeasurement.Type == SpacePlanType.Wrap)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
var childSize = new Size(
|
||||||
|
Horizontal.HasValue ? childMeasurement.Width : availableSpace.Width,
|
||||||
|
Vertical.HasValue ? childMeasurement.Height : availableSpace.Height);
|
||||||
|
|
||||||
var top = GetTopOffset(availableSpace, childSize);
|
var top = GetTopOffset(availableSpace, childSize);
|
||||||
var left = GetLeftOffset(availableSpace, childSize);
|
var left = GetLeftOffset(availableSpace, childSize);
|
||||||
|
|
||||||
@@ -36,7 +40,7 @@ namespace QuestPDF.Elements
|
|||||||
VerticalAlignment.Top => 0,
|
VerticalAlignment.Top => 0,
|
||||||
VerticalAlignment.Middle => difference / 2,
|
VerticalAlignment.Middle => difference / 2,
|
||||||
VerticalAlignment.Bottom => difference,
|
VerticalAlignment.Bottom => difference,
|
||||||
_ => throw new NotSupportedException()
|
_ => 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +53,7 @@ namespace QuestPDF.Elements
|
|||||||
HorizontalAlignment.Left => 0,
|
HorizontalAlignment.Left => 0,
|
||||||
HorizontalAlignment.Center => difference / 2,
|
HorizontalAlignment.Center => difference / 2,
|
||||||
HorizontalAlignment.Right => difference,
|
HorizontalAlignment.Right => difference,
|
||||||
_ => throw new NotSupportedException()
|
_ => 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -41,15 +41,5 @@ namespace QuestPDF.Elements
|
|||||||
{
|
{
|
||||||
return $"Border: Top({Top}) Right({Right}) Bottom({Bottom}) Left({Left}) Color({Color})";
|
return $"Border: Top({Top}) Right({Right}) Bottom({Bottom}) Left({Left}) Color({Color})";
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Collect()
|
|
||||||
{
|
|
||||||
base.Collect();
|
|
||||||
|
|
||||||
Left = 0;
|
|
||||||
Right = 0;
|
|
||||||
Bottom = 0;
|
|
||||||
Top = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19,7 +19,7 @@ namespace QuestPDF.Elements
|
|||||||
public Position Offset { get; set; }
|
public Position Offset { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class Column : Element, ICacheable, IStateResettable, ICollectable
|
internal class Column : Element, ICacheable, IStateResettable
|
||||||
{
|
{
|
||||||
internal List<ColumnItem> Items { get; } = new();
|
internal List<ColumnItem> Items { get; } = new();
|
||||||
internal float Spacing { get; set; }
|
internal float Spacing { get; set; }
|
||||||
@@ -74,7 +74,7 @@ namespace QuestPDF.Elements
|
|||||||
command.ColumnItem.IsRendered = true;
|
command.ColumnItem.IsRendered = true;
|
||||||
|
|
||||||
var targetSize = new Size(availableSpace.Width, command.Size.Height);
|
var targetSize = new Size(availableSpace.Width, command.Size.Height);
|
||||||
|
|
||||||
Canvas.Translate(command.Offset);
|
Canvas.Translate(command.Offset);
|
||||||
command.ColumnItem.Draw(targetSize);
|
command.ColumnItem.Draw(targetSize);
|
||||||
Canvas.Translate(command.Offset.Reverse());
|
Canvas.Translate(command.Offset.Reverse());
|
||||||
@@ -124,10 +124,5 @@ namespace QuestPDF.Elements
|
|||||||
|
|
||||||
return commands;
|
return commands;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Collect()
|
|
||||||
{
|
|
||||||
Items.Clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ namespace QuestPDF.Elements
|
|||||||
{
|
{
|
||||||
internal class Container : ContainerElement
|
internal class Container : ContainerElement
|
||||||
{
|
{
|
||||||
public Container()
|
internal Container()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,13 +6,14 @@ using QuestPDF.Infrastructure;
|
|||||||
|
|
||||||
namespace QuestPDF.Elements
|
namespace QuestPDF.Elements
|
||||||
{
|
{
|
||||||
internal class DynamicHost : Element, IStateResettable
|
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)
|
||||||
{
|
{
|
||||||
Child = child;
|
Child = 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);
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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,15 +118,16 @@ 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()
|
||||||
{
|
{
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@ using QuestPDF.Infrastructure;
|
|||||||
|
|
||||||
namespace QuestPDF.Elements
|
namespace QuestPDF.Elements
|
||||||
{
|
{
|
||||||
internal class Padding : ContainerElement, ICacheable, ICollectable
|
internal class Padding : ContainerElement, ICacheable
|
||||||
{
|
{
|
||||||
public float Top { get; set; }
|
public float Top { get; set; }
|
||||||
public float Right { get; set; }
|
public float Right { get; set; }
|
||||||
@@ -62,15 +62,5 @@ namespace QuestPDF.Elements
|
|||||||
{
|
{
|
||||||
return $"Padding: Top({Top}) Right({Right}) Bottom({Bottom}) Left({Left})";
|
return $"Padding: Top({Top}) Right({Right}) Bottom({Bottom}) Left({Left})";
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Collect()
|
|
||||||
{
|
|
||||||
base.Collect();
|
|
||||||
|
|
||||||
Left = 0;
|
|
||||||
Right = 0;
|
|
||||||
Bottom = 0;
|
|
||||||
Top = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -31,8 +31,10 @@ namespace QuestPDF.Elements
|
|||||||
public Position Offset { get; set; }
|
public Position Offset { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class Row : Element, ICacheable, IStateResettable, ICollectable
|
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))
|
||||||
@@ -156,10 +162,5 @@ namespace QuestPDF.Elements
|
|||||||
|
|
||||||
return renderingCommands;
|
return renderingCommands;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Collect()
|
|
||||||
{
|
|
||||||
Items.Clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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,17 +27,8 @@ 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()
|
||||||
{
|
{
|
||||||
return Cells;
|
return Cells;
|
||||||
@@ -42,11 +36,26 @@ 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()
|
||||||
{
|
{
|
||||||
@@ -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;
|
||||||
@@ -158,7 +173,7 @@ namespace QuestPDF.Elements.Table
|
|||||||
|
|
||||||
if (ExtendLastCellsToTableBottom)
|
if (ExtendLastCellsToTableBottom)
|
||||||
AdjustLastCellSizes(tableHeight, commands);
|
AdjustLastCellSizes(tableHeight, commands);
|
||||||
|
|
||||||
return commands;
|
return commands;
|
||||||
|
|
||||||
static float[] GetColumnLeftOffsets(IList<TableColumnDefinition> columns)
|
static float[] GetColumnLeftOffsets(IList<TableColumnDefinition> columns)
|
||||||
@@ -199,9 +214,9 @@ namespace QuestPDF.Elements.Table
|
|||||||
|
|
||||||
currentRow = cell.Row;
|
currentRow = cell.Row;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
@@ -218,14 +233,14 @@ namespace QuestPDF.Elements.Table
|
|||||||
{
|
{
|
||||||
maxRenderingRow = Math.Min(maxRenderingRow, cell.Row + cell.RowSpan - 1);
|
maxRenderingRow = Math.Min(maxRenderingRow, cell.Row + cell.RowSpan - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// corner case: if cell within the row want to wrap to the next page, do not attempt to render this row
|
// corner case: if cell within the row want to wrap to the next page, do not attempt to render this row
|
||||||
if (cellSize.Type == SpacePlanType.Wrap)
|
if (cellSize.Type == SpacePlanType.Wrap)
|
||||||
{
|
{
|
||||||
maxRenderingRow = Math.Min(maxRenderingRow, cell.Row - 1);
|
maxRenderingRow = Math.Min(maxRenderingRow, cell.Row - 1);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// update position of the last row that cell occupies
|
// update position of the last row that cell occupies
|
||||||
var bottomRow = cell.Row + cell.RowSpan - 1;
|
var bottomRow = cell.Row + cell.RowSpan - 1;
|
||||||
rowBottomOffsets[bottomRow] = Math.Max(rowBottomOffsets[bottomRow], topOffset + cellSize.Height);
|
rowBottomOffsets[bottomRow] = Math.Max(rowBottomOffsets[bottomRow], topOffset + cellSize.Height);
|
||||||
|
|||||||
@@ -0,0 +1,160 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using QuestPDF.Drawing;
|
||||||
|
using QuestPDF.Drawing.Exceptions;
|
||||||
|
using QuestPDF.Elements.Text.Items;
|
||||||
|
using QuestPDF.Fluent;
|
||||||
|
using QuestPDF.Infrastructure;
|
||||||
|
using SkiaSharp;
|
||||||
|
|
||||||
|
namespace QuestPDF.Elements.Text
|
||||||
|
{
|
||||||
|
internal static class FontFallback
|
||||||
|
{
|
||||||
|
public struct TextRun
|
||||||
|
{
|
||||||
|
public string Content { get; set; }
|
||||||
|
public TextStyle Style { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FallbackOption
|
||||||
|
{
|
||||||
|
public TextStyle Style { get; set; }
|
||||||
|
public SKFont Font { get; set; }
|
||||||
|
public SKTypeface Typeface { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SKFontManager FontManager => SKFontManager.Default;
|
||||||
|
|
||||||
|
public static IEnumerable<TextRun> SplitWithFontFallback(this string text, TextStyle textStyle)
|
||||||
|
{
|
||||||
|
var fallbackOptions = GetFallbackOptions(textStyle).ToArray();
|
||||||
|
|
||||||
|
var spanStartIndex = 0;
|
||||||
|
var spanFallbackOption = fallbackOptions[0];
|
||||||
|
|
||||||
|
for (var i = 0; i < text.Length; i += char.IsSurrogatePair(text, i) ? 2 : 1)
|
||||||
|
{
|
||||||
|
var codepoint = char.ConvertToUtf32(text, i);
|
||||||
|
var newFallbackOption = MatchFallbackOption(fallbackOptions, codepoint);
|
||||||
|
|
||||||
|
if (newFallbackOption == spanFallbackOption)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
yield return new TextRun
|
||||||
|
{
|
||||||
|
Content = text.Substring(spanStartIndex, i - spanStartIndex),
|
||||||
|
Style = spanFallbackOption.Style
|
||||||
|
};
|
||||||
|
|
||||||
|
spanStartIndex = i;
|
||||||
|
spanFallbackOption = newFallbackOption;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spanStartIndex > text.Length)
|
||||||
|
yield break;
|
||||||
|
|
||||||
|
yield return new TextRun
|
||||||
|
{
|
||||||
|
Content = text.Substring(spanStartIndex, text.Length - spanStartIndex),
|
||||||
|
Style = spanFallbackOption.Style
|
||||||
|
};
|
||||||
|
|
||||||
|
static IEnumerable<FallbackOption> GetFallbackOptions(TextStyle? textStyle)
|
||||||
|
{
|
||||||
|
while (textStyle != null)
|
||||||
|
{
|
||||||
|
var font = textStyle.ToFont();
|
||||||
|
|
||||||
|
yield return new FallbackOption
|
||||||
|
{
|
||||||
|
Style = textStyle,
|
||||||
|
Font = font,
|
||||||
|
Typeface = font.Typeface
|
||||||
|
};
|
||||||
|
|
||||||
|
textStyle = textStyle.Fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static FallbackOption MatchFallbackOption(ICollection<FallbackOption> fallbackOptions, int codepoint)
|
||||||
|
{
|
||||||
|
foreach (var fallbackOption in fallbackOptions)
|
||||||
|
{
|
||||||
|
if (fallbackOption.Font.ContainsGlyph(codepoint))
|
||||||
|
return fallbackOption;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw CreateNotMatchingFontException(codepoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Exception CreateNotMatchingFontException(int codepoint)
|
||||||
|
{
|
||||||
|
var character = char.ConvertFromUtf32(codepoint);
|
||||||
|
var unicode = $"U-{codepoint:X4}";
|
||||||
|
|
||||||
|
var proposedFonts = FindFontsContainingGlyph(codepoint);
|
||||||
|
var proposedFontsFormatted = proposedFonts.Any() ? string.Join(", ", proposedFonts) : "no fonts available";
|
||||||
|
|
||||||
|
return new DocumentDrawingException(
|
||||||
|
$"Could not find an appropriate font fallback for glyph: {unicode} '{character}'. " +
|
||||||
|
$"Font families available on current environment that contain this glyph: {proposedFontsFormatted}. " +
|
||||||
|
$"Possible solutions: " +
|
||||||
|
$"1) Use one of the listed fonts as the primary font in your document. " +
|
||||||
|
$"2) Configure the fallback TextStyle using the 'TextStyle.Fallback' method with one of the listed fonts. ");
|
||||||
|
}
|
||||||
|
|
||||||
|
static IEnumerable<string> FindFontsContainingGlyph(int codepoint)
|
||||||
|
{
|
||||||
|
var fontManager = SKFontManager.Default;
|
||||||
|
|
||||||
|
return fontManager
|
||||||
|
.GetFontFamilies()
|
||||||
|
.Select(fontManager.MatchFamily)
|
||||||
|
.Where(x => x.ContainsGlyph(codepoint))
|
||||||
|
.Select(x => x.FamilyName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<ITextBlockItem> ApplyFontFallback(this ICollection<ITextBlockItem> textBlockItems)
|
||||||
|
{
|
||||||
|
foreach (var textBlockItem in textBlockItems)
|
||||||
|
{
|
||||||
|
if (textBlockItem is TextBlockPageNumber or TextBlockElement)
|
||||||
|
{
|
||||||
|
yield return textBlockItem;
|
||||||
|
}
|
||||||
|
else if (textBlockItem is TextBlockSpan textBlockSpan)
|
||||||
|
{
|
||||||
|
if (!Settings.CheckIfAllTextGlyphsAreAvailable && textBlockSpan.Style.Fallback == null)
|
||||||
|
{
|
||||||
|
yield return textBlockSpan;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var textRuns = textBlockSpan.Text.SplitWithFontFallback(textBlockSpan.Style);
|
||||||
|
|
||||||
|
foreach (var textRun in textRuns)
|
||||||
|
{
|
||||||
|
var newElement = textBlockSpan switch
|
||||||
|
{
|
||||||
|
TextBlockHyperlink hyperlink => new TextBlockHyperlink { Url = hyperlink.Url },
|
||||||
|
TextBlockSectionLink sectionLink => new TextBlockSectionLink { SectionName = sectionLink.SectionName },
|
||||||
|
TextBlockSpan => new TextBlockSpan()
|
||||||
|
};
|
||||||
|
|
||||||
|
newElement.Text = textRun.Content;
|
||||||
|
newElement.Style = textRun.Style;
|
||||||
|
|
||||||
|
yield return newElement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
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, ICollectable
|
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));
|
||||||
@@ -18,8 +20,11 @@ namespace QuestPDF.Elements.Text
|
|||||||
private Queue<ITextBlockItem> RenderingQueue { get; set; }
|
private Queue<ITextBlockItem> RenderingQueue { get; set; }
|
||||||
private int CurrentElementIndex { get; set; }
|
private int CurrentElementIndex { get; set; }
|
||||||
|
|
||||||
|
private bool FontFallbackApplied { get; set; } = false;
|
||||||
|
|
||||||
public void ResetState()
|
public void ResetState()
|
||||||
{
|
{
|
||||||
|
ApplyFontFallback();
|
||||||
InitializeQueue();
|
InitializeQueue();
|
||||||
CurrentElementIndex = 0;
|
CurrentElementIndex = 0;
|
||||||
|
|
||||||
@@ -37,15 +42,31 @@ namespace QuestPDF.Elements.Text
|
|||||||
foreach (var item in Items)
|
foreach (var item in Items)
|
||||||
RenderingQueue.Enqueue(item);
|
RenderingQueue.Enqueue(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ApplyFontFallback()
|
||||||
|
{
|
||||||
|
if (FontFallbackApplied)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Items = Items.ApplyFontFallback().ToList();
|
||||||
|
FontFallbackApplied = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Collect()
|
void SetDefaultAlignment()
|
||||||
{
|
{
|
||||||
Items.Clear();
|
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);
|
||||||
|
|
||||||
@@ -73,23 +94,19 @@ 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)
|
||||||
{
|
{
|
||||||
var textDrawingRequest = new TextDrawingRequest
|
var textDrawingRequest = new TextDrawingRequest
|
||||||
@@ -104,21 +121,20 @@ namespace QuestPDF.Elements.Text
|
|||||||
TotalAscent = line.Ascent
|
TotalAscent = line.Ascent
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var canvasOffset = ContentDirection == ContentDirection.LeftToRight
|
||||||
|
? new Position(leftOffset, topOffset - line.Ascent)
|
||||||
|
: new Position(availableSpace.Width - leftOffset - item.Measurement.Width, topOffset - line.Ascent);
|
||||||
|
|
||||||
|
Canvas.Translate(canvasOffset);
|
||||||
item.Item.Draw(textDrawingRequest);
|
item.Item.Draw(textDrawingRequest);
|
||||||
|
Canvas.Translate(canvasOffset.Reverse());
|
||||||
Canvas.Translate(new Position(item.Measurement.Width, 0));
|
|
||||||
widthOffset += item.Measurement.Width;
|
leftOffset += item.Measurement.Width;
|
||||||
}
|
}
|
||||||
|
|
||||||
Canvas.Translate(new Position(-alignmentOffset, 0));
|
|
||||||
Canvas.Translate(new Position(-line.Width, line.Ascent));
|
|
||||||
Canvas.Translate(new Position(0, line.LineHeight));
|
|
||||||
|
|
||||||
heightOffset += line.LineHeight;
|
topOffset += line.LineHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
Canvas.Translate(new Position(0, -heightOffset));
|
|
||||||
|
|
||||||
lines
|
lines
|
||||||
.SelectMany(x => x.Elements)
|
.SelectMany(x => x.Elements)
|
||||||
.GroupBy(x => x.Item)
|
.GroupBy(x => x.Item)
|
||||||
@@ -135,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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using QuestPDF.Drawing;
|
|
||||||
using QuestPDF.Elements;
|
using QuestPDF.Elements;
|
||||||
using QuestPDF.Infrastructure;
|
using QuestPDF.Infrastructure;
|
||||||
|
|
||||||
@@ -9,7 +8,7 @@ namespace QuestPDF.Fluent
|
|||||||
{
|
{
|
||||||
private static IContainer Border(this IContainer element, Action<Border> handler)
|
private static IContainer Border(this IContainer element, Action<Border> handler)
|
||||||
{
|
{
|
||||||
var border = element as Border ?? ElementCacheManager.Get<Border>();
|
var border = element as Border ?? new Border();
|
||||||
handler(border);
|
handler(border);
|
||||||
|
|
||||||
return element.Element(border);
|
return element.Element(border);
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using QuestPDF.Drawing;
|
|
||||||
using QuestPDF.Elements;
|
using QuestPDF.Elements;
|
||||||
using QuestPDF.Infrastructure;
|
using QuestPDF.Infrastructure;
|
||||||
using Container = System.ComponentModel.Container;
|
|
||||||
|
|
||||||
namespace QuestPDF.Fluent
|
namespace QuestPDF.Fluent
|
||||||
{
|
{
|
||||||
public class ColumnDescriptor
|
public class ColumnDescriptor
|
||||||
{
|
{
|
||||||
internal Column Column { get; set; }
|
internal Column Column { get; } = new();
|
||||||
|
|
||||||
public void Spacing(float value, Unit unit = Unit.Point)
|
public void Spacing(float value, Unit unit = Unit.Point)
|
||||||
{
|
{
|
||||||
@@ -17,9 +15,14 @@ namespace QuestPDF.Fluent
|
|||||||
|
|
||||||
public IContainer Item()
|
public IContainer Item()
|
||||||
{
|
{
|
||||||
var columnItem = ElementCacheManager.Get<ColumnItem>();
|
var container = new Container();
|
||||||
Column.Items.Add(columnItem);
|
|
||||||
return columnItem;
|
Column.Items.Add(new ColumnItem
|
||||||
|
{
|
||||||
|
Child = container
|
||||||
|
});
|
||||||
|
|
||||||
|
return container;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,8 +36,7 @@ namespace QuestPDF.Fluent
|
|||||||
|
|
||||||
public static void Column(this IContainer element, Action<ColumnDescriptor> handler)
|
public static void Column(this IContainer element, Action<ColumnDescriptor> handler)
|
||||||
{
|
{
|
||||||
var descriptor = ElementCacheManager.Get<ColumnDescriptor>();
|
var descriptor = new ColumnDescriptor();
|
||||||
descriptor.Column = ElementCacheManager.Get<Column>();
|
|
||||||
handler(descriptor);
|
handler(descriptor);
|
||||||
element.Element(descriptor.Column);
|
element.Element(descriptor.Column);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using QuestPDF.Drawing;
|
|
||||||
using QuestPDF.Drawing.Exceptions;
|
using QuestPDF.Drawing.Exceptions;
|
||||||
using QuestPDF.Elements;
|
using QuestPDF.Elements;
|
||||||
using QuestPDF.Infrastructure;
|
using QuestPDF.Infrastructure;
|
||||||
@@ -53,10 +52,10 @@ namespace QuestPDF.Fluent
|
|||||||
|
|
||||||
public static IContainer Background(this IContainer element, string color)
|
public static IContainer Background(this IContainer element, string color)
|
||||||
{
|
{
|
||||||
var background = ElementCacheManager.Get<Background>();
|
return element.Element(new Background
|
||||||
background.Color = color;
|
{
|
||||||
|
Color = color
|
||||||
return element.Element(background);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Placeholder(this IContainer element, string? text = null)
|
public static void Placeholder(this IContainer element, string? text = null)
|
||||||
@@ -163,7 +162,7 @@ namespace QuestPDF.Fluent
|
|||||||
|
|
||||||
public static IContainer MinimalBox(this IContainer element)
|
public static IContainer MinimalBox(this IContainer element)
|
||||||
{
|
{
|
||||||
return element.Element(ElementCacheManager.Get<MinimalBox>());
|
return element.Element(new MinimalBox());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IContainer Unconstrained(this IContainer element)
|
public static IContainer Unconstrained(this IContainer element)
|
||||||
|
|||||||
@@ -55,12 +55,13 @@ 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();
|
||||||
|
|
||||||
if (element is Alignment alignment)
|
if (element is Alignment alignment && alignment.Horizontal.HasValue)
|
||||||
descriptor.Alignment(alignment.Horizontal);
|
descriptor.Alignment(alignment.Horizontal.Value);
|
||||||
|
|
||||||
handler(descriptor);
|
handler(descriptor);
|
||||||
element.Component(descriptor.Grid);
|
element.Component(descriptor.Grid);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using QuestPDF.Drawing;
|
|
||||||
using QuestPDF.Elements;
|
using QuestPDF.Elements;
|
||||||
using QuestPDF.Infrastructure;
|
using QuestPDF.Infrastructure;
|
||||||
|
|
||||||
@@ -9,7 +8,7 @@ namespace QuestPDF.Fluent
|
|||||||
{
|
{
|
||||||
private static IContainer Padding(this IContainer element, Action<Padding> handler)
|
private static IContainer Padding(this IContainer element, Action<Padding> handler)
|
||||||
{
|
{
|
||||||
var padding = element as Padding ?? ElementCacheManager.Get<Padding>();
|
var padding = element as Padding ?? new Padding();
|
||||||
handler(padding);
|
handler(padding);
|
||||||
|
|
||||||
return element.Element(padding);
|
return element.Element(padding);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
@@ -39,6 +41,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)
|
||||||
{
|
{
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using QuestPDF.Drawing;
|
|
||||||
using QuestPDF.Elements;
|
using QuestPDF.Elements;
|
||||||
using QuestPDF.Infrastructure;
|
using QuestPDF.Infrastructure;
|
||||||
|
|
||||||
@@ -7,7 +6,7 @@ namespace QuestPDF.Fluent
|
|||||||
{
|
{
|
||||||
public class RowDescriptor
|
public class RowDescriptor
|
||||||
{
|
{
|
||||||
internal Row Row { get; set; }
|
internal Row Row { get; } = new();
|
||||||
|
|
||||||
public void Spacing(float value)
|
public void Spacing(float value)
|
||||||
{
|
{
|
||||||
@@ -16,12 +15,14 @@ namespace QuestPDF.Fluent
|
|||||||
|
|
||||||
private IContainer Item(RowItemType type, float size = 0)
|
private IContainer Item(RowItemType type, float size = 0)
|
||||||
{
|
{
|
||||||
var rowItem = ElementCacheManager.Get<RowItem>();
|
var element = new RowItem
|
||||||
rowItem.Type = type;
|
{
|
||||||
rowItem.Size = size;
|
Type = type,
|
||||||
|
Size = size
|
||||||
|
};
|
||||||
|
|
||||||
Row.Items.Add(rowItem);
|
Row.Items.Add(element);
|
||||||
return rowItem;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Obsolete("This element has been renamed since version 2022.2. Please use the RelativeItem method.")]
|
[Obsolete("This element has been renamed since version 2022.2. Please use the RelativeItem method.")]
|
||||||
@@ -56,12 +57,9 @@ namespace QuestPDF.Fluent
|
|||||||
{
|
{
|
||||||
public static void Row(this IContainer element, Action<RowDescriptor> handler)
|
public static void Row(this IContainer element, Action<RowDescriptor> handler)
|
||||||
{
|
{
|
||||||
var descriptor = ElementCacheManager.Get<RowDescriptor>();
|
var descriptor = new RowDescriptor();
|
||||||
descriptor.Row = ElementCacheManager.Get<Row>();
|
|
||||||
|
|
||||||
handler(descriptor);
|
handler(descriptor);
|
||||||
element.Element(descriptor.Row);
|
element.Element(descriptor.Row);
|
||||||
ElementCacheManager.Store(descriptor);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -36,6 +36,7 @@ namespace QuestPDF.Fluent
|
|||||||
internal TextPageNumberDescriptor(Action<TextStyle> assignTextStyle, Action<PageNumberFormatter> assignFormatFunction) : base(assignTextStyle)
|
internal TextPageNumberDescriptor(Action<TextStyle> assignTextStyle, Action<PageNumberFormatter> assignFormatFunction) : base(assignTextStyle)
|
||||||
{
|
{
|
||||||
AssignFormatFunction = assignFormatFunction;
|
AssignFormatFunction = assignFormatFunction;
|
||||||
|
AssignFormatFunction(x => x?.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
public TextPageNumberDescriptor Format(PageNumberFormatter formatter)
|
public TextPageNumberDescriptor Format(PageNumberFormatter formatter)
|
||||||
@@ -49,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)
|
||||||
@@ -61,7 +62,7 @@ namespace QuestPDF.Fluent
|
|||||||
{
|
{
|
||||||
DefaultStyle = style(TextStyle.Default);
|
DefaultStyle = style(TextStyle.Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AlignLeft()
|
public void AlignLeft()
|
||||||
{
|
{
|
||||||
Alignment = HorizontalAlignment.Left;
|
Alignment = HorizontalAlignment.Left;
|
||||||
@@ -241,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);
|
||||||
@@ -275,16 +276,22 @@ namespace QuestPDF.Fluent
|
|||||||
descriptor.Compose(element);
|
descriptor.Compose(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Obsolete("This element has been renamed since version 2022.3. Please use the overload that returns a TextSpanDescriptor object which allows to specify text style.")]
|
[Obsolete("This method has been deprecated since version 2022.3. Please use the overload that returns a TextSpanDescriptor object which allows to specify text style.")]
|
||||||
public static void Text(this IContainer element, object? text, TextStyle style)
|
public static void Text(this IContainer element, object? text, TextStyle style)
|
||||||
{
|
{
|
||||||
element.Text(text).Style(style);
|
element.Text(text).Style(style);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Obsolete("This method has been deprecated since version 2022.12. Please use an overload where the text parameter is passed explicitly as a string.")]
|
||||||
public static TextSpanDescriptor Text(this IContainer element, object? text)
|
public static TextSpanDescriptor Text(this IContainer element, object? text)
|
||||||
{
|
{
|
||||||
var descriptor = (TextSpanDescriptor) null;
|
return element.Text(text?.ToString());
|
||||||
element.Text(x => descriptor = x.Span(text?.ToString()));
|
}
|
||||||
|
|
||||||
|
public static TextSpanDescriptor Text(this IContainer element, string? text)
|
||||||
|
{
|
||||||
|
var descriptor = (TextSpanDescriptor) null!;
|
||||||
|
element.Text(x => descriptor = x.Span(text));
|
||||||
return descriptor;
|
return descriptor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,17 @@ namespace QuestPDF.Fluent
|
|||||||
return descriptor;
|
return descriptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static T Fallback<T>(this T descriptor, TextStyle? value = null) where T : TextSpanDescriptor
|
||||||
|
{
|
||||||
|
descriptor.TextStyle.Fallback = value;
|
||||||
|
return descriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static T Fallback<T>(this T descriptor, Func<TextStyle, TextStyle> handler) where T : TextSpanDescriptor
|
||||||
|
{
|
||||||
|
return descriptor.Fallback(handler(TextStyle.Default));
|
||||||
|
}
|
||||||
|
|
||||||
public static T FontColor<T>(this T descriptor, string value) where T : TextSpanDescriptor
|
public static T FontColor<T>(this T descriptor, string value) where T : TextSpanDescriptor
|
||||||
{
|
{
|
||||||
descriptor.MutateTextStyle(x => x.FontColor(value));
|
descriptor.MutateTextStyle(x => x.FontColor(value));
|
||||||
@@ -44,7 +55,18 @@ namespace QuestPDF.Fluent
|
|||||||
descriptor.MutateTextStyle(x => x.LineHeight(value));
|
descriptor.MutateTextStyle(x => x.LineHeight(value));
|
||||||
return descriptor;
|
return descriptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Letter spacing controls space between characters. Value 0 corresponds to normal spacing defined by a font.
|
||||||
|
/// Positive values create additional space, whereas negative values reduce space between characters.
|
||||||
|
/// Added / reduced space is relative to the font size.
|
||||||
|
/// </summary>
|
||||||
|
public static T LetterSpacing<T>(this T descriptor, float value) where T : TextSpanDescriptor
|
||||||
|
{
|
||||||
|
descriptor.MutateTextStyle(x => x.LetterSpacing(value));
|
||||||
|
return descriptor;
|
||||||
|
}
|
||||||
|
|
||||||
public static T Italic<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor
|
public static T Italic<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor
|
||||||
{
|
{
|
||||||
descriptor.MutateTextStyle(x => x.Italic(value));
|
descriptor.MutateTextStyle(x => x.Italic(value));
|
||||||
@@ -153,5 +175,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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,8 +4,6 @@ using QuestPDF.Infrastructure;
|
|||||||
|
|
||||||
namespace QuestPDF.Fluent
|
namespace QuestPDF.Fluent
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
public static class TextStyleExtensions
|
public static class TextStyleExtensions
|
||||||
{
|
{
|
||||||
[Obsolete("This element has been renamed since version 2022.3. Please use the FontColor method.")]
|
[Obsolete("This element has been renamed since version 2022.3. Please use the FontColor method.")]
|
||||||
@@ -50,7 +48,17 @@ namespace QuestPDF.Fluent
|
|||||||
{
|
{
|
||||||
return style.Mutate(TextStyleProperty.LineHeight, value);
|
return style.Mutate(TextStyleProperty.LineHeight, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Letter spacing controls space between characters. Value 0 corresponds to normal spacing defined by a font.
|
||||||
|
/// Positive values create additional space, whereas negative values reduce space between characters.
|
||||||
|
/// Added / reduced space is relative to the font size.
|
||||||
|
/// </summary>
|
||||||
|
public static TextStyle LetterSpacing(this TextStyle style, float value)
|
||||||
|
{
|
||||||
|
return style.Mutate(TextStyleProperty.LetterSpacing, value);
|
||||||
|
}
|
||||||
|
|
||||||
public static TextStyle Italic(this TextStyle style, bool value = true)
|
public static TextStyle Italic(this TextStyle style, bool value = true)
|
||||||
{
|
{
|
||||||
return style.Mutate(TextStyleProperty.IsItalic, value);
|
return style.Mutate(TextStyleProperty.IsItalic, value);
|
||||||
@@ -131,6 +139,7 @@ namespace QuestPDF.Fluent
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Position
|
#region Position
|
||||||
|
|
||||||
public static TextStyle NormalPosition(this TextStyle style)
|
public static TextStyle NormalPosition(this TextStyle style)
|
||||||
{
|
{
|
||||||
return style.Position(FontPosition.Normal);
|
return style.Position(FontPosition.Normal);
|
||||||
@@ -150,6 +159,45 @@ namespace QuestPDF.Fluent
|
|||||||
{
|
{
|
||||||
return style.Mutate(TextStyleProperty.FontPosition, fontPosition);
|
return style.Mutate(TextStyleProperty.FontPosition, fontPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Fallback
|
||||||
|
|
||||||
|
public static TextStyle Fallback(this TextStyle style, TextStyle? value = null)
|
||||||
|
{
|
||||||
|
return style.Mutate(TextStyleProperty.Fallback, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TextStyle Fallback(this TextStyle style, Func<TextStyle, TextStyle> handler)
|
||||||
|
{
|
||||||
|
return style.Fallback(handler(TextStyle.Default));
|
||||||
|
}
|
||||||
|
|
||||||
|
#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
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,7 @@ namespace QuestPDF.Helpers
|
|||||||
{
|
{
|
||||||
public static class Placeholders
|
public static class Placeholders
|
||||||
{
|
{
|
||||||
public static readonly Random Random = new Random(0);
|
public static readonly Random Random = new Random();
|
||||||
|
|
||||||
#region Word Cache
|
#region Word Cache
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ using QuestPDF.Elements;
|
|||||||
|
|
||||||
namespace QuestPDF.Infrastructure
|
namespace QuestPDF.Infrastructure
|
||||||
{
|
{
|
||||||
internal abstract class ContainerElement : Element, IContainer, ICollectable
|
internal abstract class ContainerElement : Element, IContainer
|
||||||
{
|
{
|
||||||
internal Element? Child { get; set; } = Empty.Instance;
|
internal Element? Child { get; set; } = Empty.Instance;
|
||||||
|
|
||||||
@@ -34,10 +34,5 @@ namespace QuestPDF.Infrastructure
|
|||||||
{
|
{
|
||||||
Child?.Draw(availableSpace);
|
Child?.Draw(availableSpace);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void Collect()
|
|
||||||
{
|
|
||||||
Child = default;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
|
||||||
{
|
|
||||||
public interface ICollectable
|
|
||||||
{
|
|
||||||
void Collect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,12 +10,16 @@ namespace QuestPDF.Infrastructure
|
|||||||
internal string? FontFamily { get; set; }
|
internal string? FontFamily { get; set; }
|
||||||
internal float? Size { get; set; }
|
internal float? Size { get; set; }
|
||||||
internal float? LineHeight { get; set; }
|
internal float? LineHeight { get; set; }
|
||||||
|
internal float? LetterSpacing { get; set; }
|
||||||
internal FontWeight? FontWeight { get; set; }
|
internal FontWeight? FontWeight { get; set; }
|
||||||
internal FontPosition? FontPosition { get; set; }
|
internal FontPosition? FontPosition { get; set; }
|
||||||
internal bool? IsItalic { get; set; }
|
internal bool? IsItalic { get; set; }
|
||||||
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 static TextStyle LibraryDefault { get; } = new()
|
internal static TextStyle LibraryDefault { get; } = new()
|
||||||
{
|
{
|
||||||
@@ -24,12 +28,15 @@ namespace QuestPDF.Infrastructure
|
|||||||
FontFamily = Fonts.Lato,
|
FontFamily = Fonts.Lato,
|
||||||
Size = 12,
|
Size = 12,
|
||||||
LineHeight = 1.2f,
|
LineHeight = 1.2f,
|
||||||
|
LetterSpacing = 0,
|
||||||
FontWeight = Infrastructure.FontWeight.Normal,
|
FontWeight = Infrastructure.FontWeight.Normal,
|
||||||
FontPosition = Infrastructure.FontPosition.Normal,
|
FontPosition = Infrastructure.FontPosition.Normal,
|
||||||
IsItalic = false,
|
IsItalic = false,
|
||||||
HasStrikethrough = false,
|
HasStrikethrough = false,
|
||||||
HasUnderline = false,
|
HasUnderline = false,
|
||||||
WrapAnywhere = false
|
WrapAnywhere = false,
|
||||||
|
Direction = TextDirection.Auto,
|
||||||
|
Fallback = null
|
||||||
};
|
};
|
||||||
|
|
||||||
public static TextStyle Default { get; } = new();
|
public static TextStyle Default { get; } = new();
|
||||||
|
|||||||
@@ -11,18 +11,22 @@ namespace QuestPDF.Infrastructure
|
|||||||
FontFamily,
|
FontFamily,
|
||||||
Size,
|
Size,
|
||||||
LineHeight,
|
LineHeight,
|
||||||
|
LetterSpacing,
|
||||||
FontWeight,
|
FontWeight,
|
||||||
FontPosition,
|
FontPosition,
|
||||||
IsItalic,
|
IsItalic,
|
||||||
HasStrikethrough,
|
HasStrikethrough,
|
||||||
HasUnderline,
|
HasUnderline,
|
||||||
WrapAnywhere
|
WrapAnywhere,
|
||||||
|
Fallback,
|
||||||
|
Direction
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static class TextStyleManager
|
internal static class TextStyleManager
|
||||||
{
|
{
|
||||||
public static ConcurrentDictionary<(TextStyle origin, TextStyleProperty property, object value), TextStyle> TextStyleMutateCache = new();
|
private static readonly ConcurrentDictionary<(TextStyle origin, TextStyleProperty property, object value), TextStyle> TextStyleMutateCache = new();
|
||||||
public static ConcurrentDictionary<(TextStyle origin, TextStyle parent, bool overrideValue), TextStyle> TextStyleApplyCache = new();
|
private static readonly ConcurrentDictionary<(TextStyle origin, TextStyle parent), TextStyle> TextStyleApplyGlobalCache = new();
|
||||||
|
private static readonly ConcurrentDictionary<(TextStyle origin, TextStyle parent), TextStyle> TextStyleOverrideCache = new();
|
||||||
|
|
||||||
public static TextStyle Mutate(this TextStyle origin, TextStyleProperty property, object value)
|
public static TextStyle Mutate(this TextStyle origin, TextStyleProperty property, object value)
|
||||||
{
|
{
|
||||||
@@ -30,7 +34,7 @@ namespace QuestPDF.Infrastructure
|
|||||||
return TextStyleMutateCache.GetOrAdd(cacheKey, x => MutateStyle(x.origin, x.property, x.value));
|
return TextStyleMutateCache.GetOrAdd(cacheKey, x => MutateStyle(x.origin, x.property, x.value));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TextStyle MutateStyle(TextStyle origin, TextStyleProperty property, object value, bool overrideValue = true)
|
private static TextStyle MutateStyle(TextStyle origin, TextStyleProperty property, object? value, bool overrideValue = true)
|
||||||
{
|
{
|
||||||
if (overrideValue && value == null)
|
if (overrideValue && value == null)
|
||||||
return origin;
|
return origin;
|
||||||
@@ -99,6 +103,19 @@ namespace QuestPDF.Infrastructure
|
|||||||
|
|
||||||
return origin with { LineHeight = castedValue };
|
return origin with { LineHeight = castedValue };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(property == TextStyleProperty.LetterSpacing)
|
||||||
|
{
|
||||||
|
if (!overrideValue && origin.LetterSpacing != null)
|
||||||
|
return origin;
|
||||||
|
|
||||||
|
var castedValue = (float?)value;
|
||||||
|
|
||||||
|
if (origin.LetterSpacing == castedValue)
|
||||||
|
return origin;
|
||||||
|
|
||||||
|
return origin with { LetterSpacing = castedValue };
|
||||||
|
}
|
||||||
|
|
||||||
if (property == TextStyleProperty.FontWeight)
|
if (property == TextStyleProperty.FontWeight)
|
||||||
{
|
{
|
||||||
@@ -177,38 +194,84 @@ namespace QuestPDF.Infrastructure
|
|||||||
|
|
||||||
return origin with { WrapAnywhere = castedValue };
|
return origin with { WrapAnywhere = castedValue };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (property == TextStyleProperty.Fallback)
|
||||||
|
{
|
||||||
|
if (!overrideValue && origin.Fallback != null)
|
||||||
|
return origin;
|
||||||
|
|
||||||
|
var castedValue = (TextStyle?)value;
|
||||||
|
|
||||||
|
if (origin.Fallback == castedValue)
|
||||||
|
return origin;
|
||||||
|
|
||||||
|
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.");
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static TextStyle ApplyGlobalStyle(this TextStyle style, TextStyle parent)
|
internal static TextStyle ApplyGlobalStyle(this TextStyle style, TextStyle parent)
|
||||||
{
|
{
|
||||||
var cacheKey = (style, parent, false);
|
var cacheKey = (style, parent);
|
||||||
return TextStyleApplyCache.GetOrAdd(cacheKey, key => ApplyStyle(key.origin, key.parent, key.overrideValue));
|
return TextStyleApplyGlobalCache.GetOrAdd(cacheKey, key => ApplyStyle(key.origin, key.parent, overrideStyle: false).ApplyFontFallback());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TextStyle ApplyFontFallback(this TextStyle style)
|
||||||
|
{
|
||||||
|
var targetFallbackStyle = style
|
||||||
|
?.Fallback
|
||||||
|
?.ApplyStyle(style, overrideStyle: false, applyFallback: false)
|
||||||
|
?.ApplyFontFallback();
|
||||||
|
|
||||||
|
return MutateStyle(style, TextStyleProperty.Fallback, targetFallbackStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static TextStyle OverrideStyle(this TextStyle style, TextStyle parent)
|
internal static TextStyle OverrideStyle(this TextStyle style, TextStyle parent)
|
||||||
{
|
{
|
||||||
var cacheKey = (style, parent, true);
|
var cacheKey = (style, parent);
|
||||||
return TextStyleApplyCache.GetOrAdd(cacheKey, key => ApplyStyle(key.origin, key.parent, key.overrideValue));
|
|
||||||
|
return TextStyleOverrideCache.GetOrAdd(cacheKey, key =>
|
||||||
|
{
|
||||||
|
var result = ApplyStyle(key.origin, key.parent);
|
||||||
|
return MutateStyle(result, TextStyleProperty.Fallback, key.parent.Fallback);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TextStyle ApplyStyle(TextStyle style, TextStyle parent, bool overrideValue)
|
private static TextStyle ApplyStyle(this TextStyle style, TextStyle parent, bool overrideStyle = true, bool applyFallback = true)
|
||||||
{
|
{
|
||||||
var result = style;
|
var result = style;
|
||||||
|
|
||||||
result = MutateStyle(result, TextStyleProperty.Color, parent.Color, overrideValue);
|
result = MutateStyle(result, TextStyleProperty.Color, parent.Color, overrideStyle);
|
||||||
result = MutateStyle(result, TextStyleProperty.BackgroundColor, parent.BackgroundColor, overrideValue);
|
result = MutateStyle(result, TextStyleProperty.BackgroundColor, parent.BackgroundColor, overrideStyle);
|
||||||
result = MutateStyle(result, TextStyleProperty.FontFamily, parent.FontFamily, overrideValue);
|
result = MutateStyle(result, TextStyleProperty.FontFamily, parent.FontFamily, overrideStyle);
|
||||||
result = MutateStyle(result, TextStyleProperty.Size, parent.Size, overrideValue);
|
result = MutateStyle(result, TextStyleProperty.Size, parent.Size, overrideStyle);
|
||||||
result = MutateStyle(result, TextStyleProperty.LineHeight, parent.LineHeight, overrideValue);
|
result = MutateStyle(result, TextStyleProperty.LineHeight, parent.LineHeight, overrideStyle);
|
||||||
result = MutateStyle(result, TextStyleProperty.FontWeight, parent.FontWeight, overrideValue);
|
result = MutateStyle(result, TextStyleProperty.LetterSpacing, parent.LetterSpacing, overrideStyle);
|
||||||
result = MutateStyle(result, TextStyleProperty.FontPosition, parent.FontPosition, overrideValue);
|
result = MutateStyle(result, TextStyleProperty.FontWeight, parent.FontWeight, overrideStyle);
|
||||||
result = MutateStyle(result, TextStyleProperty.IsItalic, parent.IsItalic, overrideValue);
|
result = MutateStyle(result, TextStyleProperty.FontPosition, parent.FontPosition, overrideStyle);
|
||||||
result = MutateStyle(result, TextStyleProperty.HasStrikethrough, parent.HasStrikethrough, overrideValue);
|
result = MutateStyle(result, TextStyleProperty.IsItalic, parent.IsItalic, overrideStyle);
|
||||||
result = MutateStyle(result, TextStyleProperty.HasUnderline, parent.HasUnderline, overrideValue);
|
result = MutateStyle(result, TextStyleProperty.HasStrikethrough, parent.HasStrikethrough, overrideStyle);
|
||||||
result = MutateStyle(result, TextStyleProperty.WrapAnywhere, parent.WrapAnywhere, overrideValue);
|
result = MutateStyle(result, TextStyleProperty.HasUnderline, parent.HasUnderline, overrideStyle);
|
||||||
|
result = MutateStyle(result, TextStyleProperty.WrapAnywhere, parent.WrapAnywhere, overrideStyle);
|
||||||
|
result = MutateStyle(result, TextStyleProperty.Direction, parent.Direction, overrideStyle);
|
||||||
|
|
||||||
|
if (applyFallback)
|
||||||
|
result = MutateStyle(result, TextStyleProperty.Fallback, parent.Fallback, overrideStyle);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 = 8;
|
private const int RequiredPreviewerVersionMinor = 12;
|
||||||
|
|
||||||
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.8.2</Version>
|
<Version>2022.12.0</Version>
|
||||||
<PackageDescription>QuestPDF is an open-source, modern and battle-tested library that can help you with generating PDF documents by offering friendly, discoverable and predictable C# fluent API.</PackageDescription>
|
<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>
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
<PackageIcon>Logo.png</PackageIcon>
|
<PackageIcon>Logo.png</PackageIcon>
|
||||||
<PackageIconUrl>https://www.questpdf.com/images/package-logo.png</PackageIconUrl>
|
<PackageIconUrl>https://www.questpdf.com/images/package-logo.png</PackageIconUrl>
|
||||||
<PackageProjectUrl>https://www.questpdf.com/</PackageProjectUrl>
|
<PackageProjectUrl>https://www.questpdf.com/</PackageProjectUrl>
|
||||||
|
<PackageReadmeFile>PackageReadme.md</PackageReadmeFile>
|
||||||
<RepositoryUrl>https://github.com/QuestPDF/library.git</RepositoryUrl>
|
<RepositoryUrl>https://github.com/QuestPDF/library.git</RepositoryUrl>
|
||||||
<RepositoryType>git</RepositoryType>
|
<RepositoryType>git</RepositoryType>
|
||||||
<Copyright>Marcin Ziąbek, QuestPDF contributors</Copyright>
|
<Copyright>Marcin Ziąbek, QuestPDF contributors</Copyright>
|
||||||
@@ -23,8 +24,8 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="SkiaSharp" Version="2.80.4" />
|
<PackageReference Include="SkiaSharp" Version="2.88.3" />
|
||||||
<PackageReference Include="SkiaSharp.HarfBuzz" Version="2.80.4" />
|
<PackageReference Include="SkiaSharp.HarfBuzz" Version="2.88.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -39,11 +40,10 @@
|
|||||||
<EmbeddedResource Include="Resources\ImagePlaceholder.png" />
|
<EmbeddedResource Include="Resources\ImagePlaceholder.png" />
|
||||||
<EmbeddedResource Include="Resources\Logo.png" />
|
<EmbeddedResource Include="Resources\Logo.png" />
|
||||||
<None Remove="ImagePlaceholder.png" />
|
<None Remove="ImagePlaceholder.png" />
|
||||||
<None Include="Resources\Logo.png">
|
|
||||||
<Pack>true</Pack>
|
<None Include="Resources\Logo.png" Pack="true" PackagePath="\" />
|
||||||
<Visible>false</Visible>
|
<None Include="Resources\PackageReadme.md" Pack="true" PackagePath="\" />
|
||||||
<PackagePath>\</PackagePath>
|
|
||||||
</None>
|
|
||||||
<None Remove="Resources\DefaultFont\Lato-Black.ttf" />
|
<None Remove="Resources\DefaultFont\Lato-Black.ttf" />
|
||||||
<EmbeddedResource Include="Resources\DefaultFont\Lato-Black.ttf" />
|
<EmbeddedResource Include="Resources\DefaultFont\Lato-Black.ttf" />
|
||||||
<None Remove="Resources\DefaultFont\Lato-BlackItalic.ttf" />
|
<None Remove="Resources\DefaultFont\Lato-BlackItalic.ttf" />
|
||||||
|
|||||||
@@ -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.12.0" />
|
||||||
```
|
```
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|||||||
@@ -1,20 +1,4 @@
|
|||||||
2022.8.0:
|
Feature: implemented LetterSpacing property for the Text element
|
||||||
|
Improvement: the Text element API accepts now only string values, objects are not automatically converted anymore
|
||||||
- Improved library performance,
|
Fix: the Alignment element incorrectly limits size of its child when only one axis is set (horizontal or vertical)
|
||||||
- Breaking change: changed default font from Calibri to an open-source Lato,
|
Maintenance: Updated SkiaSharp dependency to 2.88.3
|
||||||
- Default font files are included with the nuget package, making it safe to deploy on any environment,
|
|
||||||
- Default font files are significantly smaller, so output document files should be smaller too (up to 20x reduction in size),
|
|
||||||
- When requested font is not available on the runtime environment, library provides list of available fonts,
|
|
||||||
- Fixed a rare layout overflow exception with the Inlined element,
|
|
||||||
- Fixed a memory leak connected to the HarfBuzz library.
|
|
||||||
|
|
||||||
|
|
||||||
2022.8.1:
|
|
||||||
- Fixed: default text style does not always work
|
|
||||||
- Fixed: page breaking rendering does not work in very specific corner cases
|
|
||||||
- Stability improvements for text wrapping
|
|
||||||
- Updated stability of rendering elements in negative space
|
|
||||||
- Optimization for the Column element: do not measure child when available height is negative
|
|
||||||
|
|
||||||
2022.8.2
|
|
||||||
- Fixed: the Column element incorrectly renders zero-height elements.
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
public static class Settings
|
public static class Settings
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This value represents the maximum lenght of the document that the library produces.
|
/// This value represents the maximum length of the document that the library produces.
|
||||||
/// This is useful when layout constraints are too strong, e.g. one element does not fit in another.
|
/// This is useful when layout constraints are too strong, e.g. one element does not fit in another.
|
||||||
/// In such cases, the library would produce document of infinite length, consuming all available resources.
|
/// In such cases, the library would produce document of infinite length, consuming all available resources.
|
||||||
/// To break the algorithm and save the environment, the library breaks the rendering process after reaching specified length of document.
|
/// To break the algorithm and save the environment, the library breaks the rendering process after reaching specified length of document.
|
||||||
@@ -35,6 +35,6 @@
|
|||||||
/// However, it provides hints that used fonts are not sufficient to produce correct results.
|
/// However, it provides hints that used fonts are not sufficient to produce correct results.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>By default, this flag is enabled only when the debugger IS attached.</remarks>
|
/// <remarks>By default, this flag is enabled only when the debugger IS attached.</remarks>
|
||||||
public static bool CheckIfAllTextGlyphsAreAvailableInSpecifiedFont { get; set; } = System.Diagnostics.Debugger.IsAttached;
|
public static bool CheckIfAllTextGlyphsAreAvailable { get; set; } = System.Diagnostics.Debugger.IsAttached;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,13 +17,14 @@ 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.
|
||||||
|
|
||||||
⭐ Please give this repository a star. It takes seconds and help thousands of developers! ⭐
|
⭐ Please give this repository a star. It takes seconds and help thousands of developers! ⭐
|
||||||
|
|
||||||
<img src="https://user-images.githubusercontent.com/9263853/184642026-27dd7567-a46a-45d4-9594-e6a70a7193e9.png" width="700" />
|
<img src="https://user-images.githubusercontent.com/9263853/207596765-f3b84375-e4da-4597-b6a4-f2e22dd2b479.png" width="800" />
|
||||||
|
|
||||||
|
|
||||||
## Please share with the community
|
## Please share with the community
|
||||||
|
|
||||||
@@ -46,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)
|
||||||
|
|
||||||
@@ -61,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.12.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.
|
||||||
|
|
||||||
|
|
||||||
@@ -76,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