Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 09e642295f |
@@ -1,51 +0,0 @@
|
||||
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
|
||||
@@ -1,29 +0,0 @@
|
||||
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");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,408 +0,0 @@
|
||||
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,5 +1,4 @@
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
@@ -44,7 +43,7 @@ namespace QuestPDF.Examples
|
||||
.Height(50)
|
||||
.AlignCenter()
|
||||
.AlignMiddle()
|
||||
.Text(i.ToString(CultureInfo.InvariantCulture))
|
||||
.Text(i)
|
||||
.FontSize(16 + i / 4);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Elements;
|
||||
@@ -127,9 +126,9 @@ namespace QuestPDF.Examples
|
||||
.Padding(5)
|
||||
.Row(row =>
|
||||
{
|
||||
row.ConstantItem(30).Text((index + 1).ToString(CultureInfo.InvariantCulture));
|
||||
row.ConstantItem(30).Text(index + 1);
|
||||
row.RelativeItem().Text(item.ItemName);
|
||||
row.ConstantItem(50).AlignRight().Text(item.Count.ToString(CultureInfo.InvariantCulture));
|
||||
row.ConstantItem(50).AlignRight().Text(item.Count);
|
||||
row.ConstantItem(50).AlignRight().Text($"{item.Price}$");
|
||||
row.ConstantItem(50).AlignRight().Text($"{item.Count*item.Price}$");
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Elements;
|
||||
@@ -97,9 +96,9 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
var item = Items[index];
|
||||
|
||||
table.Cell().Element(Style).Text((index + 1).ToString(CultureInfo.InvariantCulture));
|
||||
table.Cell().Element(Style).Text(index + 1);
|
||||
table.Cell().Element(Style).Text(item.ItemName);
|
||||
table.Cell().Element(Style).AlignRight().Text(item.Count.ToString(CultureInfo.InvariantCulture));
|
||||
table.Cell().Element(Style).AlignRight().Text(item.Count);
|
||||
table.Cell().Element(Style).AlignRight().Text($"{item.Price}$");
|
||||
table.Cell().Element(Style).AlignRight().Text($"{item.Count*item.Price}$");
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.2" />
|
||||
<PackageReference Include="microcharts" Version="0.9.5.9" />
|
||||
<PackageReference Include="nunit" Version="3.13.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.88.3" />
|
||||
<PackageReference Include="Svg.Skia" Version="0.5.18" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.80.4" />
|
||||
<PackageReference Include="Svg.Skia" Version="0.5.10" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class RepeatContentExamples
|
||||
{
|
||||
[Test]
|
||||
public void ItemTypes()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProducePdf()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(25)
|
||||
.Decoration(decoration =>
|
||||
{
|
||||
decoration.Before().Text("Test").FontSize(22);
|
||||
|
||||
decoration.Content().Column(column =>
|
||||
{
|
||||
column.Spacing(20);
|
||||
|
||||
foreach (var _ in Enumerable.Range(0, 10))
|
||||
column.Item().Background(Colors.Grey.Medium).ExtendHorizontal().Height(80);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Drawing;
|
||||
@@ -390,12 +389,12 @@ namespace QuestPDF.Examples
|
||||
table.Cell().Element(CellStyle).ExtendHorizontal().AlignLeft().Text(page.name);
|
||||
|
||||
// inches
|
||||
table.Cell().Element(CellStyle).Text(page.width.ToString(CultureInfo.InvariantCulture));
|
||||
table.Cell().Element(CellStyle).Text(page.height.ToString(CultureInfo.InvariantCulture));
|
||||
table.Cell().Element(CellStyle).Text(page.width);
|
||||
table.Cell().Element(CellStyle).Text(page.height);
|
||||
|
||||
// points
|
||||
table.Cell().Element(CellStyle).Text((page.width * inchesToPoints).ToString(CultureInfo.InvariantCulture));
|
||||
table.Cell().Element(CellStyle).Text((page.height * inchesToPoints).ToString(CultureInfo.InvariantCulture));
|
||||
table.Cell().Element(CellStyle).Text(page.width * inchesToPoints);
|
||||
table.Cell().Element(CellStyle).Text(page.height * inchesToPoints);
|
||||
|
||||
IContainer CellStyle(IContainer container) => DefaultCellStyle(container, Colors.White);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using NUnit.Framework;
|
||||
@@ -122,132 +122,7 @@ 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]
|
||||
public void SuperscriptSubscript_Simple()
|
||||
{
|
||||
@@ -648,7 +523,7 @@ namespace QuestPDF.Examples
|
||||
|
||||
page.Content().Column(column =>
|
||||
{
|
||||
column.Item().Text((string) null);
|
||||
column.Item().Text(null);
|
||||
|
||||
column.Item().Text(text =>
|
||||
{
|
||||
@@ -680,7 +555,7 @@ namespace QuestPDF.Examples
|
||||
|
||||
page.Content().Column(column =>
|
||||
{
|
||||
column.Item().Text((string) null);
|
||||
column.Item().Text(null);
|
||||
|
||||
column.Item().Text(text =>
|
||||
{
|
||||
@@ -729,7 +604,7 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(250, 100)
|
||||
.PageSize(500, 100)
|
||||
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
@@ -739,9 +614,9 @@ namespace QuestPDF.Examples
|
||||
.Padding(25)
|
||||
.MinimalBox()
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
.Text("خوارزمية ترتيب")
|
||||
.Text("ينا الألم. في بعض الأحيان ونظراً للالتزامات التي يفرضها علينا")
|
||||
.FontFamily(Fonts.Calibri)
|
||||
.FontSize(30);
|
||||
.FontSize(20);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -814,102 +689,5 @@ namespace QuestPDF.Examples
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[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>
|
||||
<Company>CodeFlint</Company>
|
||||
<PackageId>QuestPDF.Previewer</PackageId>
|
||||
<Version>2022.12.0</Version>
|
||||
<Version>2022.9.1</Version>
|
||||
<PackAsTool>true</PackAsTool>
|
||||
<ToolCommandName>questpdf-previewer</ToolCommandName>
|
||||
<PackageDescription>QuestPDF is an open-source, modern and battle-tested library that can help you with generating PDF documents by offering friendly, discoverable and predictable C# fluent API.</PackageDescription>
|
||||
@@ -13,7 +13,6 @@
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<PackageIcon>Logo.png</PackageIcon>
|
||||
<PackageIconUrl>https://www.questpdf.com/images/package-logo.png</PackageIconUrl>
|
||||
<PackageReadmeFile>PackageReadme.md</PackageReadmeFile>
|
||||
<PackageProjectUrl>https://www.questpdf.com/</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/QuestPDF/library.git</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
@@ -34,22 +33,23 @@
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\Logo.png" />
|
||||
<None Include="Resources\Logo.png" Pack="true" PackagePath="\" />
|
||||
<None Include="Resources\PackageReadme.md" Pack="true" PackagePath="\" />
|
||||
<None Include="Resources\Logo.png">
|
||||
<Pack>true</Pack>
|
||||
<Visible>false</Visible>
|
||||
<PackagePath>\</PackagePath>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="Avalonia" Version="0.10.18" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="0.10.18" />
|
||||
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.18" />
|
||||
<PackageReference Include="Avalonia.Markup.Xaml.Loader" Version="0.10.18" />
|
||||
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.18" />
|
||||
<PackageReference Include="ReactiveUI" Version="18.4.1" />
|
||||
<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="Avalonia" Version="0.10.10" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="0.10.10" />
|
||||
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.10" />
|
||||
<PackageReference Include="Avalonia.Markup.Xaml.Loader" Version="0.10.10" />
|
||||
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.10" />
|
||||
<PackageReference Include="ReactiveUI" Version="17.1.50" />
|
||||
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="2.80.4" />
|
||||
<PackageReference Include="System.Reactive" Version="5.0.0" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.88.3" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.80.4" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
[](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,11 +68,9 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
|
||||
var lengthStyle = TextStyle.Default.FontColor(Colors.Grey.Medium);
|
||||
|
||||
text.TotalPagesWithinSection(locationName).Style(lengthStyle).Format(x =>
|
||||
{
|
||||
var formatted = x == 1 ? "1 page long" : $"{x} pages long";
|
||||
return $" ({formatted})";
|
||||
});
|
||||
text.Span(" (").Style(lengthStyle);
|
||||
text.TotalPagesWithinSection(locationName).Style(lengthStyle).Format(x => x == 1 ? "1 page long" : $"{x} pages long");
|
||||
text.Span(")").Style(lengthStyle);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
|
||||
<PackageReference Include="nunit" Version="3.13.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.88.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.80.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -86,43 +86,5 @@ namespace QuestPDF.UnitTests
|
||||
.ExpectCanvasTranslate(new Position(-300, 0))
|
||||
.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,9 +153,7 @@ namespace QuestPDF.UnitTests
|
||||
Ratio = 2f
|
||||
})
|
||||
.DrawElement(new Size(500, 200))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.ExpectChildDraw(new Size(400, 200))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
@@ -170,45 +168,7 @@ namespace QuestPDF.UnitTests
|
||||
Ratio = 2f
|
||||
})
|
||||
.DrawElement(new Size(400, 300))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.ExpectChildDraw(new Size(400, 200))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DrawChild_PerWidth_RightToLeft()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new AspectRatio
|
||||
{
|
||||
Child = x.CreateChild(),
|
||||
Option = AspectRatioOption.FitArea,
|
||||
Ratio = 2f,
|
||||
ContentDirection = ContentDirection.RightToLeft
|
||||
})
|
||||
.DrawElement(new Size(500, 200))
|
||||
.ExpectCanvasTranslate(100, 0)
|
||||
.ExpectChildDraw(new Size(400, 200))
|
||||
.ExpectCanvasTranslate(-100, 0)
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DrawChild_PerHeight_RightToLeft()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new AspectRatio
|
||||
{
|
||||
Child = x.CreateChild(),
|
||||
Option = AspectRatioOption.FitArea,
|
||||
Ratio = 2f,
|
||||
ContentDirection = ContentDirection.RightToLeft
|
||||
})
|
||||
.DrawElement(new Size(400, 300))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.ExpectChildDraw(new Size(400, 200))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.CheckDrawResult();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,9 +35,7 @@ namespace QuestPDF.UnitTests
|
||||
})
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure(expectedInput: new Size(400, 300), returns: SpacePlan.PartialRender(200, 100))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.ExpectChildDraw(new Size(200, 100))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
@@ -51,43 +49,7 @@ namespace QuestPDF.UnitTests
|
||||
})
|
||||
.MeasureElement(new Size(500, 400))
|
||||
.ExpectChildMeasure(expectedInput: new Size(500, 400), returns: SpacePlan.FullRender(300, 200))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.ExpectChildDraw(new Size(300, 200))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_PartialRender_RightToLeft()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new MinimalBox
|
||||
{
|
||||
Child = x.CreateChild(),
|
||||
ContentDirection = ContentDirection.RightToLeft
|
||||
})
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure(expectedInput: new Size(400, 300), returns: SpacePlan.PartialRender(200, 100))
|
||||
.ExpectCanvasTranslate(200, 0)
|
||||
.ExpectChildDraw(new Size(200, 100))
|
||||
.ExpectCanvasTranslate(-200, 0)
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_FullRender_RightToLeft()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new MinimalBox
|
||||
{
|
||||
Child = x.CreateChild(),
|
||||
ContentDirection = ContentDirection.RightToLeft
|
||||
})
|
||||
.MeasureElement(new Size(500, 400))
|
||||
.ExpectChildMeasure(expectedInput: new Size(500, 400), returns: SpacePlan.FullRender(350, 200))
|
||||
.ExpectCanvasTranslate(150, 0)
|
||||
.ExpectChildDraw(new Size(350, 200))
|
||||
.ExpectCanvasTranslate(-150, 0)
|
||||
.CheckDrawResult();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.8.0" />
|
||||
<PackageReference Include="FluentAssertions" Version="6.7.0" />
|
||||
<PackageReference Include="nunit" Version="3.13.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.88.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.80.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -195,7 +195,7 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
|
||||
public TestPlan CheckMeasureResult(SpacePlan expected)
|
||||
{
|
||||
Element.InjectDependencies(null, Canvas);
|
||||
Element.VisitChildren(x => x?.Initialize(null, Canvas));
|
||||
|
||||
var actual = Element.Measure(OperationInput);
|
||||
|
||||
@@ -210,7 +210,7 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
|
||||
public TestPlan CheckDrawResult()
|
||||
{
|
||||
Element.InjectDependencies(null, Canvas);
|
||||
Element.VisitChildren(x => x?.Initialize(null, Canvas));
|
||||
Element.Draw(OperationInput);
|
||||
return this;
|
||||
}
|
||||
@@ -253,10 +253,10 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
availableSpace ??= new Size(400, 300);
|
||||
|
||||
var canvas = new FreeCanvas();
|
||||
value.InjectDependencies(null, canvas);
|
||||
value.VisitChildren(x => x.Initialize(null, canvas));
|
||||
var valueMeasure = value.Measure(availableSpace.Value);
|
||||
|
||||
expected.InjectDependencies(null, canvas);
|
||||
expected.VisitChildren(x => x.Initialize(null, canvas));
|
||||
var expectedMeasure = expected.Measure(availableSpace.Value);
|
||||
|
||||
valueMeasure.Should().BeEquivalentTo(expectedMeasure);
|
||||
@@ -267,11 +267,11 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
availableSpace ??= new Size(400, 300);
|
||||
|
||||
var valueCanvas = new OperationRecordingCanvas();
|
||||
value.InjectDependencies(null, valueCanvas);
|
||||
value.VisitChildren(x => x.Initialize(null, valueCanvas));
|
||||
value.Draw(availableSpace.Value);
|
||||
|
||||
var expectedCanvas = new OperationRecordingCanvas();
|
||||
expected.InjectDependencies(null, expectedCanvas);
|
||||
expected.VisitChildren(x => x.Initialize(null, expectedCanvas));
|
||||
expected.Draw(availableSpace.Value);
|
||||
|
||||
valueCanvas.Operations.Should().BeEquivalentTo(expectedCanvas.Operations);
|
||||
|
||||
@@ -77,9 +77,7 @@ namespace QuestPDF.UnitTests
|
||||
})
|
||||
.DrawElement(new Size(900, 800))
|
||||
.ExpectChildMeasure(Size.Max, SpacePlan.PartialRender(1200, 1600))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.ExpectChildDraw(new Size(1200, 1600))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
@@ -93,43 +91,7 @@ namespace QuestPDF.UnitTests
|
||||
})
|
||||
.DrawElement(new Size(900, 800))
|
||||
.ExpectChildMeasure(Size.Max, SpacePlan.FullRender(1600, 1000))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.ExpectChildDraw(new Size(1600, 1000))
|
||||
.ExpectCanvasTranslate(0, 0)
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Draw_WhenChildPartiallyRenders_RightToLeft()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Unconstrained
|
||||
{
|
||||
Child = x.CreateChild(),
|
||||
ContentDirection = ContentDirection.RightToLeft
|
||||
})
|
||||
.DrawElement(new Size(900, 800))
|
||||
.ExpectChildMeasure(Size.Max, SpacePlan.PartialRender(1200, 1600))
|
||||
.ExpectCanvasTranslate(-1200, 0)
|
||||
.ExpectChildDraw(new Size(1200, 1600))
|
||||
.ExpectCanvasTranslate(1200, 0)
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Draw_WhenChildFullyRenders_RightToLeft()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Unconstrained
|
||||
{
|
||||
Child = x.CreateChild(),
|
||||
ContentDirection = ContentDirection.RightToLeft
|
||||
})
|
||||
.DrawElement(new Size(900, 800))
|
||||
.ExpectChildMeasure(Size.Max, SpacePlan.FullRender(1600, 1000))
|
||||
.ExpectCanvasTranslate(-1600, 0)
|
||||
.ExpectChildDraw(new Size(1600, 1000))
|
||||
.ExpectCanvasTranslate(1600, 0)
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
|
||||
@@ -65,14 +65,14 @@ namespace QuestPDF.Drawing
|
||||
document.Compose(container);
|
||||
var content = container.Compose();
|
||||
ApplyDefaultTextStyle(content, TextStyle.LibraryDefault);
|
||||
ApplyContentDirection(content, ContentDirection.LeftToRight);
|
||||
|
||||
ApplyContentRepeatState(content, false);
|
||||
|
||||
var debuggingState = Settings.EnableDebugging ? ApplyDebugging(content) : null;
|
||||
|
||||
if (Settings.EnableCaching)
|
||||
ApplyCaching(content);
|
||||
|
||||
var pageContext = new PageContext();
|
||||
var pageContext = new PageContext();
|
||||
RenderPass(pageContext, new FreeCanvas(), content, debuggingState);
|
||||
RenderPass(pageContext, canvas, content, debuggingState);
|
||||
}
|
||||
@@ -80,8 +80,10 @@ namespace QuestPDF.Drawing
|
||||
internal static void RenderPass<TCanvas>(PageContext pageContext, TCanvas canvas, Container content, DebuggingState? debuggingState)
|
||||
where TCanvas : ICanvas, IRenderingCanvas
|
||||
{
|
||||
InjectDependencies(content, pageContext, canvas);
|
||||
content.VisitChildren(x => x?.Initialize(pageContext, canvas));
|
||||
content.VisitChildren(x => (x as IStateResettable)?.ResetState());
|
||||
|
||||
ResetIsRenderedState(content);
|
||||
|
||||
canvas.BeginDocument();
|
||||
|
||||
@@ -141,18 +143,6 @@ 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)
|
||||
{
|
||||
content.VisitChildren(x =>
|
||||
@@ -174,22 +164,48 @@ namespace QuestPDF.Drawing
|
||||
return debuggingState;
|
||||
}
|
||||
|
||||
internal static void ApplyContentDirection(this Element? content, ContentDirection direction)
|
||||
private static void ApplyContentRepeatState(Element? content, bool repeatContent)
|
||||
{
|
||||
if (content == null)
|
||||
return;
|
||||
|
||||
if (content is ContentDirectionSetter contentDirectionSetter)
|
||||
|
||||
if (content is IVisual visual)
|
||||
visual.RepeatContent = repeatContent;
|
||||
|
||||
if (content is TextBlock textBlock)
|
||||
{
|
||||
ApplyContentDirection(contentDirectionSetter.Child, contentDirectionSetter.ContentDirection);
|
||||
foreach (var textBlockItem in textBlock.Items)
|
||||
{
|
||||
if (textBlockItem is TextBlockElement textElement)
|
||||
{
|
||||
ApplyContentRepeatState(textElement.Element, true);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (content is IContentDirectionAware contentDirectionAware)
|
||||
contentDirectionAware.ContentDirection = direction;
|
||||
// TODO: apply RepeatState in dynamic content
|
||||
//if (content is DynamicHost dynamicHost)
|
||||
// dynamicHost.TextStyle = dynamicHost.TextStyle.ApplyGlobalStyle(documentDefaultTextStyle);
|
||||
|
||||
if (content is RepeatContentSetter repeatContentSetter)
|
||||
repeatContent = repeatContentSetter.RepeatContent;
|
||||
|
||||
foreach (var child in content.GetChildren())
|
||||
ApplyContentDirection(child, direction);
|
||||
ApplyContentRepeatState(child, repeatContent);
|
||||
}
|
||||
|
||||
private static void ResetIsRenderedState(Element? content)
|
||||
{
|
||||
if (content == null)
|
||||
return;
|
||||
|
||||
if (content is IVisual visual)
|
||||
visual.IsRendered = false;
|
||||
|
||||
foreach (var child in content.GetChildren())
|
||||
ResetIsRenderedState(child);
|
||||
}
|
||||
|
||||
internal static void ApplyDefaultTextStyle(this Element? content, TextStyle documentDefaultTextStyle)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using HarfBuzzSharp;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Infrastructure;
|
||||
using SkiaSharp;
|
||||
using Buffer = HarfBuzzSharp.Buffer;
|
||||
@@ -30,12 +29,6 @@ namespace QuestPDF.Drawing
|
||||
PopulateBufferWithText(buffer, text);
|
||||
buffer.GuessSegmentProperties();
|
||||
|
||||
if (TextStyle.Direction == TextDirection.LeftToRight)
|
||||
buffer.Direction = Direction.LeftToRight;
|
||||
|
||||
if (TextStyle.Direction == TextDirection.RightToLeft)
|
||||
buffer.Direction = Direction.RightToLeft;
|
||||
|
||||
ShaperFont.Shape(buffer);
|
||||
|
||||
var length = buffer.Length;
|
||||
@@ -48,34 +41,22 @@ namespace QuestPDF.Drawing
|
||||
var xOffset = 0f;
|
||||
var yOffset = 0f;
|
||||
|
||||
// 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++)
|
||||
{
|
||||
// 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
|
||||
{
|
||||
Codepoint = (ushort)glyphInfos[i].Codepoint,
|
||||
Position = new SKPoint(xOffset + glyphPositions[i].XOffset * scaleX, yOffset - glyphPositions[i].YOffset * scaleY),
|
||||
Width = glyphPositions[i].XAdvance * scaleX
|
||||
};
|
||||
};
|
||||
|
||||
xOffset += glyphPositions[i].XAdvance * scaleX;
|
||||
yOffset += glyphPositions[i].YAdvance * scaleY;
|
||||
}
|
||||
|
||||
return new TextShapingResult(buffer.Direction, glyphs);
|
||||
return new TextShapingResult(glyphs);
|
||||
}
|
||||
|
||||
void PopulateBufferWithText(Buffer buffer, string text)
|
||||
@@ -111,65 +92,29 @@ namespace QuestPDF.Drawing
|
||||
|
||||
internal class TextShapingResult
|
||||
{
|
||||
private Direction Direction { get; }
|
||||
private ShapedGlyph[] Glyphs { get; }
|
||||
public ShapedGlyph[] Glyphs { get; }
|
||||
|
||||
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)
|
||||
public TextShapingResult(ShapedGlyph[] glyphs)
|
||||
{
|
||||
Direction = direction;
|
||||
Glyphs = glyphs;
|
||||
}
|
||||
|
||||
public int BreakText(int startIndex, float maxWidth)
|
||||
{
|
||||
return Direction switch
|
||||
var index = startIndex;
|
||||
maxWidth += Glyphs[startIndex].Position.X;
|
||||
|
||||
while (index < Glyphs.Length)
|
||||
{
|
||||
Direction.LeftToRight => BreakTextLeftToRight(),
|
||||
Direction.RightToLeft => BreakTextRightToLeft(),
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
int BreakTextLeftToRight()
|
||||
{
|
||||
var index = startIndex;
|
||||
maxWidth += Glyphs[startIndex].Position.X;
|
||||
|
||||
while (index < Glyphs.Length)
|
||||
{
|
||||
var glyph = Glyphs[index];
|
||||
var glyph = Glyphs[index];
|
||||
|
||||
if (glyph.Position.X + glyph.Width > maxWidth + Size.Epsilon)
|
||||
break;
|
||||
if (glyph.Position.X + glyph.Width > maxWidth + Size.Epsilon)
|
||||
break;
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
return index - 1;
|
||||
index++;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
return index - 1;
|
||||
}
|
||||
|
||||
public float MeasureWidth(int startIndex, int endIndex)
|
||||
@@ -177,15 +122,10 @@ namespace QuestPDF.Drawing
|
||||
if (Glyphs.Length == 0)
|
||||
return 0;
|
||||
|
||||
var start = this[startIndex];
|
||||
var end = this[endIndex];
|
||||
var start = Glyphs[startIndex];
|
||||
var end = Glyphs[endIndex];
|
||||
|
||||
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()
|
||||
};
|
||||
return end.Position.X - start.Position.X + end.Width;
|
||||
}
|
||||
|
||||
public DrawTextCommand? PositionText(int startIndex, int endIndex, TextStyle textStyle)
|
||||
@@ -206,18 +146,14 @@ namespace QuestPDF.Drawing
|
||||
{
|
||||
var runIndex = sourceIndex - startIndex;
|
||||
|
||||
glyphSpan[runIndex] = this[sourceIndex].Codepoint;
|
||||
positionSpan[runIndex] = this[sourceIndex].Position;
|
||||
glyphSpan[runIndex] = Glyphs[sourceIndex].Codepoint;
|
||||
positionSpan[runIndex] = Glyphs[sourceIndex].Position;
|
||||
}
|
||||
|
||||
var firstVisualCharacterIndex = Direction == Direction.LeftToRight
|
||||
? startIndex
|
||||
: endIndex;
|
||||
|
||||
return new DrawTextCommand
|
||||
{
|
||||
SkTextBlob = skTextBlobBuilder.Build(),
|
||||
TextOffsetX = -this[firstVisualCharacterIndex].Position.X
|
||||
TextOffsetX = -Glyphs[startIndex].Position.X
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,23 +6,19 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
internal class Alignment : ContainerElement
|
||||
{
|
||||
public VerticalAlignment? Vertical { get; set; }
|
||||
public HorizontalAlignment? Horizontal { get; set; }
|
||||
public VerticalAlignment Vertical { get; set; } = VerticalAlignment.Top;
|
||||
public HorizontalAlignment Horizontal { get; set; } = HorizontalAlignment.Left;
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
if (Child == null)
|
||||
return;
|
||||
|
||||
var childMeasurement = base.Measure(availableSpace);
|
||||
var childSize = base.Measure(availableSpace);
|
||||
|
||||
if (childMeasurement.Type == SpacePlanType.Wrap)
|
||||
if (childSize.Type == SpacePlanType.Wrap)
|
||||
return;
|
||||
|
||||
var childSize = new Size(
|
||||
Horizontal.HasValue ? childMeasurement.Width : availableSpace.Width,
|
||||
Vertical.HasValue ? childMeasurement.Height : availableSpace.Height);
|
||||
|
||||
|
||||
var top = GetTopOffset(availableSpace, childSize);
|
||||
var left = GetLeftOffset(availableSpace, childSize);
|
||||
|
||||
@@ -40,7 +36,7 @@ namespace QuestPDF.Elements
|
||||
VerticalAlignment.Top => 0,
|
||||
VerticalAlignment.Middle => difference / 2,
|
||||
VerticalAlignment.Bottom => difference,
|
||||
_ => 0
|
||||
_ => throw new NotSupportedException()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -53,7 +49,7 @@ namespace QuestPDF.Elements
|
||||
HorizontalAlignment.Left => 0,
|
||||
HorizontalAlignment.Center => difference / 2,
|
||||
HorizontalAlignment.Right => difference,
|
||||
_ => 0
|
||||
_ => throw new NotSupportedException()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,8 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class AspectRatio : ContainerElement, ICacheable, IContentDirectionAware
|
||||
internal class AspectRatio : ContainerElement, ICacheable
|
||||
{
|
||||
public ContentDirection ContentDirection { get; set; }
|
||||
|
||||
public float Ratio { get; set; } = 1;
|
||||
public AspectRatioOption Option { get; set; } = AspectRatioOption.FitWidth;
|
||||
|
||||
@@ -44,14 +42,7 @@ namespace QuestPDF.Elements
|
||||
return;
|
||||
|
||||
var size = GetTargetSize(availableSpace);
|
||||
|
||||
var offset = ContentDirection == ContentDirection.LeftToRight
|
||||
? Position.Zero
|
||||
: new Position(availableSpace.Width - size.Width, 0);
|
||||
|
||||
Canvas.Translate(offset);
|
||||
base.Draw(size);
|
||||
Canvas.Translate(offset.Reverse());
|
||||
}
|
||||
|
||||
private Size GetTargetSize(Size availableSpace)
|
||||
|
||||
@@ -7,19 +7,28 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
public delegate void DrawOnCanvas(SKCanvas canvas, Size availableSpace);
|
||||
|
||||
internal class Canvas : Element, ICacheable
|
||||
internal class Canvas : Element, IVisual, ICacheable
|
||||
{
|
||||
public bool IsRendered { get; set; }
|
||||
public bool RepeatContent { get; set; }
|
||||
|
||||
public DrawOnCanvas Handler { get; set; }
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
return availableSpace.IsNegative()
|
||||
? SpacePlan.Wrap()
|
||||
: SpacePlan.FullRender(availableSpace);
|
||||
if (availableSpace.IsNegative())
|
||||
return SpacePlan.Wrap();
|
||||
|
||||
if (IsRendered && !RepeatContent)
|
||||
return SpacePlan.FullRender(Size.Zero);
|
||||
|
||||
return SpacePlan.FullRender(availableSpace);
|
||||
}
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
IsRendered = true;
|
||||
|
||||
var skiaCanvas = (Canvas as Drawing.SkiaCanvasBase)?.Canvas;
|
||||
|
||||
if (Handler == null || skiaCanvas == null)
|
||||
|
||||
@@ -74,7 +74,7 @@ namespace QuestPDF.Elements
|
||||
command.ColumnItem.IsRendered = true;
|
||||
|
||||
var targetSize = new Size(availableSpace.Width, command.Size.Height);
|
||||
|
||||
|
||||
Canvas.Translate(command.Offset);
|
||||
command.ColumnItem.Draw(targetSize);
|
||||
Canvas.Translate(command.Offset.Reverse());
|
||||
|
||||
@@ -5,10 +5,8 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class Constrained : ContainerElement, ICacheable, IContentDirectionAware
|
||||
internal class Constrained : ContainerElement, ICacheable
|
||||
{
|
||||
public ContentDirection ContentDirection { get; set; }
|
||||
|
||||
public float? MinWidth { get; set; }
|
||||
public float? MaxWidth { get; set; }
|
||||
|
||||
@@ -47,17 +45,11 @@ namespace QuestPDF.Elements
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
var size = new Size(
|
||||
var available = new Size(
|
||||
Min(MaxWidth, availableSpace.Width),
|
||||
Min(MaxHeight, availableSpace.Height));
|
||||
|
||||
var offset = ContentDirection == ContentDirection.LeftToRight
|
||||
? Position.Zero
|
||||
: new Position(availableSpace.Width - size.Width, 0);
|
||||
|
||||
Canvas.Translate(offset);
|
||||
base.Draw(size);
|
||||
Canvas.Translate(offset.Reverse());
|
||||
Child?.Draw(available);
|
||||
}
|
||||
|
||||
private static float Min(float? x, float y)
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class ContentDirectionSetter : ContainerElement
|
||||
{
|
||||
public ContentDirection ContentDirection { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -14,10 +14,8 @@ namespace QuestPDF.Elements
|
||||
public Position Offset { get; set; }
|
||||
}
|
||||
|
||||
internal class Decoration : Element, ICacheable, IContentDirectionAware
|
||||
internal class Decoration : Element, ICacheable
|
||||
{
|
||||
public ContentDirection ContentDirection { get; set; }
|
||||
|
||||
internal Element Before { get; set; } = new Empty();
|
||||
internal Element Content { get; set; } = new Empty();
|
||||
internal Element After { get; set; } = new Empty();
|
||||
@@ -66,13 +64,9 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
var elementSize = new Size(width, command.Measurement.Height);
|
||||
|
||||
var offset = ContentDirection == ContentDirection.LeftToRight
|
||||
? command.Offset
|
||||
: new Position(availableSpace.Width - width, command.Offset.Y);
|
||||
|
||||
Canvas.Translate(offset);
|
||||
Canvas.Translate(command.Offset);
|
||||
command.Element.Draw(elementSize);
|
||||
Canvas.Translate(offset.Reverse());
|
||||
Canvas.Translate(command.Offset.Reverse());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,14 +6,13 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class DynamicHost : Element, IStateResettable, IContentDirectionAware
|
||||
internal class DynamicHost : Element, IStateResettable
|
||||
{
|
||||
private DynamicComponentProxy Child { get; }
|
||||
private object InitialComponentState { get; set; }
|
||||
|
||||
internal TextStyle TextStyle { get; set; } = TextStyle.Default;
|
||||
public ContentDirection ContentDirection { get; set; }
|
||||
|
||||
|
||||
public DynamicHost(DynamicComponentProxy child)
|
||||
{
|
||||
Child = child;
|
||||
@@ -52,14 +51,12 @@ namespace QuestPDF.Elements
|
||||
|
||||
var context = new DynamicContext
|
||||
{
|
||||
PageContext = PageContext,
|
||||
Canvas = Canvas,
|
||||
|
||||
TextStyle = TextStyle,
|
||||
ContentDirection = ContentDirection,
|
||||
|
||||
PageNumber = PageContext.CurrentPage,
|
||||
TotalPages = PageContext.GetLocation(Infrastructure.PageContext.DocumentLocation).PageEnd,
|
||||
PageContext = PageContext,
|
||||
Canvas = Canvas,
|
||||
TextStyle = TextStyle,
|
||||
|
||||
AvailableSize = availableSize
|
||||
};
|
||||
|
||||
@@ -76,9 +73,7 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
internal IPageContext PageContext { get; set; }
|
||||
internal ICanvas Canvas { get; set; }
|
||||
|
||||
internal TextStyle TextStyle { get; set; }
|
||||
internal ContentDirection ContentDirection { get; set; }
|
||||
|
||||
public int PageNumber { get; internal set; }
|
||||
public int TotalPages { get; internal set; }
|
||||
@@ -90,9 +85,7 @@ namespace QuestPDF.Elements
|
||||
content(container);
|
||||
|
||||
container.ApplyDefaultTextStyle(TextStyle);
|
||||
container.ApplyContentDirection(ContentDirection);
|
||||
|
||||
container.InjectDependencies(PageContext, Canvas);
|
||||
container.VisitChildren(x => x?.Initialize(PageContext, Canvas));
|
||||
container.VisitChildren(x => (x as IStateResettable)?.ResetState());
|
||||
|
||||
container.Size = container.Measure(Size.Max);
|
||||
|
||||
@@ -6,19 +6,31 @@ using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class DynamicImage : Element
|
||||
internal class DynamicImage : Element, IVisual
|
||||
{
|
||||
public bool IsRendered { get; set; }
|
||||
public bool RepeatContent { get; set; }
|
||||
|
||||
public Func<Size, byte[]>? Source { get; set; }
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
return availableSpace.IsNegative()
|
||||
? SpacePlan.Wrap()
|
||||
: SpacePlan.FullRender(availableSpace);
|
||||
if (availableSpace.IsNegative())
|
||||
return SpacePlan.Wrap();
|
||||
|
||||
if (IsRendered && !RepeatContent)
|
||||
return SpacePlan.FullRender(Size.Zero);
|
||||
|
||||
return SpacePlan.FullRender(availableSpace);
|
||||
}
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
IsRendered = true;
|
||||
|
||||
if (availableSpace.Width < Size.Epsilon || availableSpace.Height < Size.Epsilon)
|
||||
return;
|
||||
|
||||
var imageData = Source?.Invoke(availableSpace);
|
||||
|
||||
if (imageData == null)
|
||||
|
||||
@@ -5,8 +5,11 @@ using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class Image : Element, ICacheable
|
||||
internal class Image : Element, IVisual, ICacheable
|
||||
{
|
||||
public bool IsRendered { get; set; }
|
||||
public bool RepeatContent { get; set; }
|
||||
|
||||
public SKImage? InternalImage { get; set; }
|
||||
|
||||
~Image()
|
||||
@@ -16,9 +19,13 @@ namespace QuestPDF.Elements
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
return availableSpace.IsNegative()
|
||||
? SpacePlan.Wrap()
|
||||
: SpacePlan.FullRender(availableSpace);
|
||||
if (availableSpace.IsNegative())
|
||||
return SpacePlan.Wrap();
|
||||
|
||||
if (IsRendered && !RepeatContent)
|
||||
return SpacePlan.FullRender(Size.Zero);
|
||||
|
||||
return SpacePlan.FullRender(availableSpace);
|
||||
}
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
@@ -26,6 +33,7 @@ namespace QuestPDF.Elements
|
||||
if (InternalImage == null)
|
||||
return;
|
||||
|
||||
IsRendered = true;
|
||||
Canvas.DrawImage(InternalImage, Position.Zero, availableSpace);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,17 +26,15 @@ namespace QuestPDF.Elements
|
||||
public SpacePlan Size { get; set; }
|
||||
}
|
||||
|
||||
internal class Inlined : Element, IStateResettable, IContentDirectionAware
|
||||
internal class Inlined : Element, IStateResettable
|
||||
{
|
||||
public ContentDirection ContentDirection { get; set; }
|
||||
|
||||
public List<InlinedElement> Elements { get; internal set; } = new List<InlinedElement>();
|
||||
private Queue<InlinedElement> ChildrenQueue { get; set; }
|
||||
|
||||
internal float VerticalSpacing { get; set; }
|
||||
internal float HorizontalSpacing { get; set; }
|
||||
|
||||
internal InlinedAlignment? ElementsAlignment { get; set; }
|
||||
internal InlinedAlignment ElementsAlignment { get; set; }
|
||||
internal VerticalAlignment BaselineAlignment { get; set; }
|
||||
|
||||
public void ResetState()
|
||||
@@ -51,8 +49,6 @@ namespace QuestPDF.Elements
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
SetDefaultAlignment();
|
||||
|
||||
if (!ChildrenQueue.Any())
|
||||
return SpacePlan.FullRender(Size.Zero);
|
||||
|
||||
@@ -85,8 +81,6 @@ namespace QuestPDF.Elements
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
SetDefaultAlignment();
|
||||
|
||||
var lines = Compose(availableSpace);
|
||||
var topOffset = 0f;
|
||||
|
||||
@@ -109,6 +103,7 @@ namespace QuestPDF.Elements
|
||||
|
||||
var elementOffset = ElementOffset();
|
||||
var leftOffset = AlignOffset();
|
||||
Canvas.Translate(new Position(leftOffset, 0));
|
||||
|
||||
foreach (var measurement in lineMeasurements)
|
||||
{
|
||||
@@ -118,16 +113,15 @@ namespace QuestPDF.Elements
|
||||
if (size.Height == 0)
|
||||
size = new Size(size.Width, lineSize.Height);
|
||||
|
||||
var offset = ContentDirection == ContentDirection.LeftToRight
|
||||
? new Position(leftOffset, baselineOffset)
|
||||
: new Position(availableSpace.Width - size.Width - leftOffset, baselineOffset);
|
||||
|
||||
Canvas.Translate(offset);
|
||||
Canvas.Translate(new Position(0, baselineOffset));
|
||||
measurement.Element.Draw(size);
|
||||
Canvas.Translate(offset.Reverse());
|
||||
Canvas.Translate(new Position(0, -baselineOffset));
|
||||
|
||||
leftOffset += size.Width + elementOffset;
|
||||
Canvas.Translate(new Position(size.Width + elementOffset, 0));
|
||||
}
|
||||
|
||||
Canvas.Translate(new Position(-leftOffset, 0));
|
||||
|
||||
float ElementOffset()
|
||||
{
|
||||
@@ -146,15 +140,15 @@ namespace QuestPDF.Elements
|
||||
|
||||
float AlignOffset()
|
||||
{
|
||||
var emptySpace = availableSpace.Width - lineSize.Width - (lineMeasurements.Count - 1) * HorizontalSpacing;
|
||||
var difference = availableSpace.Width - lineSize.Width - (lineMeasurements.Count - 1) * HorizontalSpacing;
|
||||
|
||||
return ElementsAlignment switch
|
||||
{
|
||||
InlinedAlignment.Left => ContentDirection == ContentDirection.LeftToRight ? 0 : emptySpace,
|
||||
InlinedAlignment.Left => 0,
|
||||
InlinedAlignment.Justify => 0,
|
||||
InlinedAlignment.SpaceAround => elementOffset,
|
||||
InlinedAlignment.Center => emptySpace / 2,
|
||||
InlinedAlignment.Right => ContentDirection == ContentDirection.LeftToRight ? emptySpace : 0,
|
||||
InlinedAlignment.Center => difference / 2,
|
||||
InlinedAlignment.Right => difference,
|
||||
_ => 0
|
||||
};
|
||||
}
|
||||
@@ -173,16 +167,6 @@ namespace QuestPDF.Elements
|
||||
}
|
||||
}
|
||||
|
||||
void SetDefaultAlignment()
|
||||
{
|
||||
if (ElementsAlignment.HasValue)
|
||||
return;
|
||||
|
||||
ElementsAlignment = ContentDirection == ContentDirection.LeftToRight
|
||||
? InlinedAlignment.Left
|
||||
: InlinedAlignment.Right;
|
||||
}
|
||||
|
||||
Size GetLineSize(ICollection<InlinedMeasurement> measurements)
|
||||
{
|
||||
var width = measurements.Sum(x => x.Size.Width);
|
||||
|
||||
@@ -15,34 +15,42 @@ namespace QuestPDF.Elements
|
||||
Horizontal
|
||||
}
|
||||
|
||||
internal class Line : Element, ILine, ICacheable
|
||||
internal class Line : Element, ILine, IVisual, ICacheable
|
||||
{
|
||||
public bool IsRendered { get; set; }
|
||||
public bool RepeatContent { get; set; }
|
||||
|
||||
public LineType Type { get; set; } = LineType.Vertical;
|
||||
public string Color { get; set; } = Colors.Black;
|
||||
public float Size { get; set; } = 1;
|
||||
public float Thickness { get; set; } = 1;
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
if (availableSpace.IsNegative())
|
||||
return SpacePlan.Wrap();
|
||||
|
||||
if (IsRendered && !RepeatContent)
|
||||
return SpacePlan.FullRender(Size.Zero);
|
||||
|
||||
return Type switch
|
||||
{
|
||||
LineType.Vertical when availableSpace.Width + Infrastructure.Size.Epsilon >= Size => SpacePlan.FullRender(Size, 0),
|
||||
LineType.Horizontal when availableSpace.Height + Infrastructure.Size.Epsilon >= Size => SpacePlan.FullRender(0, Size),
|
||||
LineType.Vertical when availableSpace.Width + Infrastructure.Size.Epsilon >= Thickness => SpacePlan.FullRender(Thickness, 0),
|
||||
LineType.Horizontal when availableSpace.Height + Infrastructure.Size.Epsilon >= Thickness => SpacePlan.FullRender(0, Thickness),
|
||||
_ => SpacePlan.Wrap()
|
||||
};
|
||||
}
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
IsRendered = true;
|
||||
|
||||
if (Type == LineType.Vertical)
|
||||
{
|
||||
Canvas.DrawRectangle(new Position(-Size/2, 0), new Size(Size, availableSpace.Height), Color);
|
||||
Canvas.DrawRectangle(new Position(-Thickness/2, 0), new Size(Thickness, availableSpace.Height), Color);
|
||||
}
|
||||
else if (Type == LineType.Horizontal)
|
||||
{
|
||||
Canvas.DrawRectangle(new Position(0, -Size/2), new Size(availableSpace.Width, Size), Color);
|
||||
Canvas.DrawRectangle(new Position(0, -Thickness/2), new Size(availableSpace.Width, Thickness), Color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,8 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class MinimalBox : ContainerElement, IContentDirectionAware
|
||||
internal class MinimalBox : ContainerElement
|
||||
{
|
||||
public ContentDirection ContentDirection { get; set; }
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
var targetSize = base.Measure(availableSpace);
|
||||
@@ -14,13 +12,7 @@ namespace QuestPDF.Elements
|
||||
if (targetSize.Type == SpacePlanType.Wrap)
|
||||
return;
|
||||
|
||||
var translate = ContentDirection == ContentDirection.RightToLeft
|
||||
? new Position(availableSpace.Width - targetSize.Width, 0)
|
||||
: Position.Zero;
|
||||
|
||||
Canvas.Translate(translate);
|
||||
base.Draw(targetSize);
|
||||
Canvas.Translate(translate.Reverse());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,7 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
internal class Page : IComponent
|
||||
{
|
||||
public ContentDirection ContentDirection { get; set; }
|
||||
public TextStyle DefaultTextStyle { get; set; } = TextStyle.Default;
|
||||
public TextStyle DefaultTextStyle { get; set; } = new TextStyle();
|
||||
|
||||
public Size MinSize { get; set; } = PageSizes.A4;
|
||||
public Size MaxSize { get; set; } = PageSizes.A4;
|
||||
@@ -31,7 +30,6 @@ namespace QuestPDF.Elements
|
||||
public void Compose(IContainer container)
|
||||
{
|
||||
container
|
||||
.ContentDirection(ContentDirection)
|
||||
.Background(BackgroundColor)
|
||||
.Layers(layers =>
|
||||
{
|
||||
|
||||
@@ -25,7 +25,6 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Text))
|
||||
x.MaxHeight(32).Image(ImageData, ImageScaling.FitArea);
|
||||
|
||||
else
|
||||
x.Text(Text).FontSize(14);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class RepeatContentSetter : ContainerElement
|
||||
{
|
||||
public bool RepeatContent { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -31,10 +31,8 @@ namespace QuestPDF.Elements
|
||||
public Position Offset { get; set; }
|
||||
}
|
||||
|
||||
internal class Row : Element, ICacheable, IStateResettable, IContentDirectionAware
|
||||
internal class Row : Element, ICacheable, IStateResettable
|
||||
{
|
||||
public ContentDirection ContentDirection { get; set; }
|
||||
|
||||
internal List<RowItem> Items { get; } = new();
|
||||
internal float Spacing { get; set; }
|
||||
|
||||
@@ -93,13 +91,9 @@ namespace QuestPDF.Elements
|
||||
if (command.Measurement.Type == SpacePlanType.Wrap)
|
||||
continue;
|
||||
|
||||
var offset = ContentDirection == ContentDirection.LeftToRight
|
||||
? command.Offset
|
||||
: new Position(availableSpace.Width - command.Offset.X - command.Size.Width, 0);
|
||||
|
||||
Canvas.Translate(offset);
|
||||
Canvas.Translate(command.Offset);
|
||||
command.RowItem.Draw(command.Size);
|
||||
Canvas.Translate(offset.Reverse());
|
||||
Canvas.Translate(command.Offset.Reverse());
|
||||
}
|
||||
|
||||
if (Items.All(x => x.IsRendered))
|
||||
|
||||
@@ -9,15 +9,12 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements.Table
|
||||
{
|
||||
internal class Table : Element, IStateResettable, IContentDirectionAware
|
||||
internal class Table : Element, IStateResettable
|
||||
{
|
||||
public ContentDirection ContentDirection { get; set; }
|
||||
|
||||
public List<TableColumnDefinition> Columns { get; set; } = new();
|
||||
public List<TableCell> Cells { get; set; } = new();
|
||||
public List<TableColumnDefinition> Columns { get; set; } = new List<TableColumnDefinition>();
|
||||
public List<TableCell> Cells { get; set; } = new List<TableCell>();
|
||||
public bool ExtendLastCellsToTableBottom { get; set; }
|
||||
|
||||
private bool CacheInitialized { get; set; }
|
||||
private int StartingRowsCount { get; set; }
|
||||
private int RowsCount { get; set; }
|
||||
private int CurrentRow { get; set; }
|
||||
@@ -29,6 +26,16 @@ namespace QuestPDF.Elements.Table
|
||||
private int MaxRow { get; set; }
|
||||
private int MaxRowSpan { get; set; }
|
||||
|
||||
internal override void Initialize(IPageContext pageContext, ICanvas canvas)
|
||||
{
|
||||
StartingRowsCount = Cells.Select(x => x.Row).DefaultIfEmpty(0).Max();
|
||||
RowsCount = Cells.Select(x => x.Row + x.RowSpan - 1).DefaultIfEmpty(0).Max();
|
||||
Cells = Cells.OrderBy(x => x.Row).ThenBy(x => x.Column).ToList();
|
||||
BuildCache();
|
||||
|
||||
base.Initialize(pageContext, canvas);
|
||||
}
|
||||
|
||||
internal override IEnumerable<Element?> GetChildren()
|
||||
{
|
||||
return Cells;
|
||||
@@ -36,26 +43,11 @@ namespace QuestPDF.Elements.Table
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
Initialize();
|
||||
|
||||
foreach (var x in Cells)
|
||||
x.IsRendered = false;
|
||||
|
||||
CurrentRow = 1;
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
if (CacheInitialized)
|
||||
return;
|
||||
|
||||
StartingRowsCount = Cells.Select(x => x.Row).DefaultIfEmpty(0).Max();
|
||||
RowsCount = Cells.Select(x => x.Row + x.RowSpan - 1).DefaultIfEmpty(0).Max();
|
||||
Cells = Cells.OrderBy(x => x.Row).ThenBy(x => x.Column).ToList();
|
||||
BuildCache();
|
||||
|
||||
CacheInitialized = true;
|
||||
}
|
||||
|
||||
private void BuildCache()
|
||||
{
|
||||
@@ -120,13 +112,9 @@ namespace QuestPDF.Elements.Table
|
||||
if (command.Measurement.Type == SpacePlanType.Wrap)
|
||||
continue;
|
||||
|
||||
var offset = ContentDirection == ContentDirection.LeftToRight
|
||||
? command.Offset
|
||||
: new Position(availableSpace.Width - command.Offset.X - command.Size.Width, command.Offset.Y);
|
||||
|
||||
Canvas.Translate(offset);
|
||||
Canvas.Translate(command.Offset);
|
||||
command.Cell.Draw(command.Size);
|
||||
Canvas.Translate(offset.Reverse());
|
||||
Canvas.Translate(command.Offset.Reverse());
|
||||
}
|
||||
|
||||
CurrentRow = FindLastRenderedRow(renderingCommands) + 1;
|
||||
@@ -173,7 +161,7 @@ namespace QuestPDF.Elements.Table
|
||||
|
||||
if (ExtendLastCellsToTableBottom)
|
||||
AdjustLastCellSizes(tableHeight, commands);
|
||||
|
||||
|
||||
return commands;
|
||||
|
||||
static float[] GetColumnLeftOffsets(IList<TableColumnDefinition> columns)
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace QuestPDF.Elements.Text.Items
|
||||
public TextMeasurementResult? Measure(TextMeasurementRequest request)
|
||||
{
|
||||
Element.VisitChildren(x => (x as IStateResettable)?.ResetState());
|
||||
Element.InjectDependencies(request.PageContext, request.Canvas);
|
||||
Element.VisitChildren(x => x.Initialize(request.PageContext, request.Canvas));
|
||||
|
||||
var measurement = Element.Measure(new Size(request.AvailableWidth, Size.Max.Height));
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace QuestPDF.Elements.Text.Items
|
||||
public void Draw(TextDrawingRequest request)
|
||||
{
|
||||
Element.VisitChildren(x => (x as IStateResettable)?.ResetState());
|
||||
Element.InjectDependencies(request.PageContext, request.Canvas);
|
||||
Element.VisitChildren(x => x.Initialize(request.PageContext, request.Canvas));
|
||||
|
||||
request.Canvas.Translate(new Position(0, request.TotalAscent));
|
||||
Element.Draw(new Size(request.TextSize.Width, -request.TotalAscent));
|
||||
|
||||
@@ -47,11 +47,11 @@ namespace QuestPDF.Elements.Text.Items
|
||||
// ignore leading spaces
|
||||
if (!request.IsFirstElementInBlock && request.IsFirstElementInLine)
|
||||
{
|
||||
while (startIndex < TextShapingResult.Length && Text[startIndex] == spaceCodepoint)
|
||||
while (startIndex < TextShapingResult.Glyphs.Length && Text[startIndex] == spaceCodepoint)
|
||||
startIndex++;
|
||||
}
|
||||
|
||||
if (TextShapingResult.Length == 0 || startIndex == TextShapingResult.Length)
|
||||
if (TextShapingResult.Glyphs.Length == 0 || startIndex == TextShapingResult.Glyphs.Length)
|
||||
{
|
||||
return new TextMeasurementResult
|
||||
{
|
||||
@@ -90,7 +90,7 @@ namespace QuestPDF.Elements.Text.Items
|
||||
StartIndex = startIndex,
|
||||
EndIndex = wrappedText.Value.endIndex,
|
||||
NextIndex = wrappedText.Value.nextIndex,
|
||||
TotalIndex = TextShapingResult.Length - 1
|
||||
TotalIndex = TextShapingResult.Glyphs.Length - 1
|
||||
};
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ namespace QuestPDF.Elements.Text.Items
|
||||
// textLength - length of the part of the text that fits in available width (creating a line)
|
||||
|
||||
// entire text fits, no need to wrap
|
||||
if (endIndex == TextShapingResult.Length - 1)
|
||||
if (endIndex == TextShapingResult.Glyphs.Length - 1)
|
||||
return (endIndex, endIndex);
|
||||
|
||||
// breaking anywhere
|
||||
@@ -110,7 +110,7 @@ namespace QuestPDF.Elements.Text.Items
|
||||
return (endIndex, endIndex + 1);
|
||||
|
||||
// current line ends at word, next character is space, perfect place to wrap
|
||||
if (TextShapingResult[endIndex].Codepoint != spaceCodepoint && TextShapingResult[endIndex + 1].Codepoint == spaceCodepoint)
|
||||
if (TextShapingResult.Glyphs[endIndex].Codepoint != spaceCodepoint && TextShapingResult.Glyphs[endIndex + 1].Codepoint == spaceCodepoint)
|
||||
return (endIndex, endIndex + 2);
|
||||
|
||||
// find last space within the available text to wrap
|
||||
@@ -118,7 +118,7 @@ namespace QuestPDF.Elements.Text.Items
|
||||
|
||||
while (lastSpaceIndex >= startIndex)
|
||||
{
|
||||
if (TextShapingResult[lastSpaceIndex].Codepoint == spaceCodepoint)
|
||||
if (TextShapingResult.Glyphs[lastSpaceIndex].Codepoint == spaceCodepoint)
|
||||
break;
|
||||
|
||||
lastSpaceIndex--;
|
||||
|
||||
@@ -4,15 +4,17 @@ using System.Linq;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Elements.Text.Calculation;
|
||||
using QuestPDF.Elements.Text.Items;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements.Text
|
||||
{
|
||||
internal class TextBlock : Element, IStateResettable, IContentDirectionAware
|
||||
internal class TextBlock : Element, IVisual, IStateResettable
|
||||
{
|
||||
public ContentDirection ContentDirection { get; set; }
|
||||
public bool IsRendered { get; set; }
|
||||
public bool RepeatContent { get; set; }
|
||||
|
||||
public HorizontalAlignment? Alignment { get; set; }
|
||||
public HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
|
||||
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));
|
||||
@@ -52,21 +54,15 @@ namespace QuestPDF.Elements.Text
|
||||
FontFallbackApplied = true;
|
||||
}
|
||||
}
|
||||
|
||||
void SetDefaultAlignment()
|
||||
{
|
||||
if (Alignment.HasValue)
|
||||
return;
|
||||
|
||||
Alignment = ContentDirection == ContentDirection.LeftToRight
|
||||
? HorizontalAlignment.Left
|
||||
: HorizontalAlignment.Right;
|
||||
}
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
SetDefaultAlignment();
|
||||
if (availableSpace.IsNegative())
|
||||
return SpacePlan.Wrap();
|
||||
|
||||
if (IsRendered && !RepeatContent)
|
||||
return SpacePlan.FullRender(Size.Zero);
|
||||
|
||||
if (!RenderingQueue.Any())
|
||||
return SpacePlan.FullRender(Size.Zero);
|
||||
|
||||
@@ -94,19 +90,26 @@ namespace QuestPDF.Elements.Text
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
SetDefaultAlignment();
|
||||
|
||||
if (IsRendered && !RepeatContent)
|
||||
return;
|
||||
|
||||
var lines = DivideTextItemsIntoLines(availableSpace.Width, availableSpace.Height).ToList();
|
||||
|
||||
if (!lines.Any())
|
||||
return;
|
||||
|
||||
var topOffset = 0f;
|
||||
|
||||
var heightOffset = 0f;
|
||||
var widthOffset = 0f;
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var leftOffset = GetAlignmentOffset(line.Width);
|
||||
widthOffset = 0f;
|
||||
|
||||
var alignmentOffset = GetAlignmentOffset(line.Width);
|
||||
|
||||
Canvas.Translate(new Position(alignmentOffset, 0));
|
||||
Canvas.Translate(new Position(0, -line.Ascent));
|
||||
|
||||
foreach (var item in line.Elements)
|
||||
{
|
||||
var textDrawingRequest = new TextDrawingRequest
|
||||
@@ -121,20 +124,21 @@ namespace QuestPDF.Elements.Text
|
||||
TotalAscent = line.Ascent
|
||||
};
|
||||
|
||||
var canvasOffset = ContentDirection == ContentDirection.LeftToRight
|
||||
? new Position(leftOffset, topOffset - line.Ascent)
|
||||
: new Position(availableSpace.Width - leftOffset - item.Measurement.Width, topOffset - line.Ascent);
|
||||
|
||||
Canvas.Translate(canvasOffset);
|
||||
item.Item.Draw(textDrawingRequest);
|
||||
Canvas.Translate(canvasOffset.Reverse());
|
||||
|
||||
leftOffset += item.Measurement.Width;
|
||||
}
|
||||
|
||||
topOffset += line.LineHeight;
|
||||
Canvas.Translate(new Position(item.Measurement.Width, 0));
|
||||
widthOffset += 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;
|
||||
}
|
||||
|
||||
|
||||
Canvas.Translate(new Position(0, -heightOffset));
|
||||
|
||||
lines
|
||||
.SelectMany(x => x.Elements)
|
||||
.GroupBy(x => x.Item)
|
||||
@@ -145,21 +149,27 @@ namespace QuestPDF.Elements.Text
|
||||
|
||||
var lastElementMeasurement = lines.Last().Elements.Last().Measurement;
|
||||
CurrentElementIndex = lastElementMeasurement.IsLast ? 0 : lastElementMeasurement.NextIndex;
|
||||
|
||||
|
||||
if (!RenderingQueue.Any())
|
||||
{
|
||||
ResetState();
|
||||
|
||||
IsRendered = true;
|
||||
}
|
||||
|
||||
float GetAlignmentOffset(float lineWidth)
|
||||
{
|
||||
if (Alignment == HorizontalAlignment.Left)
|
||||
return 0;
|
||||
|
||||
var emptySpace = availableSpace.Width - lineWidth;
|
||||
|
||||
return Alignment switch
|
||||
{
|
||||
HorizontalAlignment.Left => ContentDirection == ContentDirection.LeftToRight ? 0 : emptySpace,
|
||||
HorizontalAlignment.Center => emptySpace / 2,
|
||||
HorizontalAlignment.Right => ContentDirection == ContentDirection.LeftToRight ? emptySpace : 0,
|
||||
_ => 0
|
||||
};
|
||||
if (Alignment == HorizontalAlignment.Right)
|
||||
return emptySpace;
|
||||
|
||||
if (Alignment == HorizontalAlignment.Center)
|
||||
return emptySpace / 2;
|
||||
|
||||
throw new ArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,8 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class Unconstrained : ContainerElement, IContentDirectionAware, ICacheable
|
||||
internal class Unconstrained : ContainerElement, ICacheable
|
||||
{
|
||||
public ContentDirection ContentDirection { get; set; }
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
var childSize = base.Measure(Size.Max);
|
||||
@@ -27,13 +25,7 @@ namespace QuestPDF.Elements
|
||||
if (measurement.Type == SpacePlanType.Wrap)
|
||||
return;
|
||||
|
||||
var translate = ContentDirection == ContentDirection.RightToLeft
|
||||
? new Position(-measurement.Width, 0)
|
||||
: Position.Zero;
|
||||
|
||||
Canvas.Translate(translate);
|
||||
base.Draw(measurement);
|
||||
Canvas.Translate(translate.Reverse());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Fluent
|
||||
{
|
||||
public static class ContentDirectionExtensions
|
||||
{
|
||||
internal static IContainer ContentDirection(this IContainer element, ContentDirection direction)
|
||||
{
|
||||
return element.Element(new ContentDirectionSetter
|
||||
{
|
||||
ContentDirection = direction
|
||||
});
|
||||
}
|
||||
|
||||
public static IContainer ContentFromLeftToRight(this IContainer element)
|
||||
{
|
||||
return element.ContentDirection(Infrastructure.ContentDirection.LeftToRight);
|
||||
}
|
||||
|
||||
public static IContainer ContentFromRightToLeft(this IContainer element)
|
||||
{
|
||||
return element.ContentDirection(Infrastructure.ContentDirection.RightToLeft);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
var container = new Container();
|
||||
Decoration.Before = container;
|
||||
return container;
|
||||
return container.RepeatContentWhenPaging();
|
||||
}
|
||||
|
||||
public void Before(Action<IContainer> handler)
|
||||
@@ -36,7 +36,7 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
var container = new Container();
|
||||
Decoration.After = container;
|
||||
return container;
|
||||
return container.RepeatContentWhenPaging();
|
||||
}
|
||||
|
||||
public void After(Action<IContainer> handler)
|
||||
@@ -49,9 +49,7 @@ namespace QuestPDF.Fluent
|
||||
[Obsolete("This element has been renamed since version 2022.2. Please use the 'Before' method.")]
|
||||
public IContainer Header()
|
||||
{
|
||||
var container = new Container();
|
||||
Decoration.Before = container;
|
||||
return container;
|
||||
return Before();
|
||||
}
|
||||
|
||||
[Obsolete("This element has been renamed since version 2022.2. Please use the 'Before' method.")]
|
||||
@@ -63,9 +61,7 @@ namespace QuestPDF.Fluent
|
||||
[Obsolete("This element has been renamed since version 2022.2. Please use the 'After' method.")]
|
||||
public IContainer Footer()
|
||||
{
|
||||
var container = new Container();
|
||||
Decoration.After = container;
|
||||
return container;
|
||||
return After();
|
||||
}
|
||||
|
||||
[Obsolete("This element has been renamed since version 2022.2. Please use the 'After' method.")]
|
||||
|
||||
@@ -55,13 +55,12 @@ namespace QuestPDF.Fluent
|
||||
|
||||
public static class GridExtensions
|
||||
{
|
||||
[Obsolete("This element has been deprecated since version 2022.11. Please use the Table element, or the combination of the Row and Column elements.")]
|
||||
public static void Grid(this IContainer element, Action<GridDescriptor> handler)
|
||||
{
|
||||
var descriptor = new GridDescriptor();
|
||||
|
||||
if (element is Alignment alignment && alignment.Horizontal.HasValue)
|
||||
descriptor.Alignment(alignment.Horizontal.Value);
|
||||
if (element is Alignment alignment)
|
||||
descriptor.Alignment(alignment.Horizontal);
|
||||
|
||||
handler(descriptor);
|
||||
element.Component(descriptor.Grid);
|
||||
|
||||
@@ -30,7 +30,6 @@ namespace QuestPDF.Fluent
|
||||
public void BaselineMiddle() => Inlined.BaselineAlignment = VerticalAlignment.Middle;
|
||||
public void BaselineBottom() => Inlined.BaselineAlignment = VerticalAlignment.Bottom;
|
||||
|
||||
internal void Alignment(InlinedAlignment? alignment) => Inlined.ElementsAlignment = alignment;
|
||||
public void AlignLeft() => Inlined.ElementsAlignment = InlinedAlignment.Left;
|
||||
public void AlignCenter() => Inlined.ElementsAlignment = InlinedAlignment.Center;
|
||||
public void AlignRight() => Inlined.ElementsAlignment = InlinedAlignment.Right;
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace QuestPDF.Fluent
|
||||
return container;
|
||||
}
|
||||
|
||||
public IContainer Layer() => Layer(false);
|
||||
public IContainer Layer() => Layer(false).RepeatContentWhenPaging();
|
||||
public IContainer PrimaryLayer() => Layer(true);
|
||||
|
||||
internal void Validate()
|
||||
|
||||
@@ -6,11 +6,11 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
public static class LineExtensions
|
||||
{
|
||||
private static ILine Line(this IContainer element, LineType type, float size)
|
||||
private static ILine Line(this IContainer element, LineType type, float thickness)
|
||||
{
|
||||
var line = new Line
|
||||
{
|
||||
Size = size,
|
||||
Thickness = thickness,
|
||||
Type = type
|
||||
};
|
||||
|
||||
@@ -18,14 +18,14 @@ namespace QuestPDF.Fluent
|
||||
return line;
|
||||
}
|
||||
|
||||
public static ILine LineVertical(this IContainer element, float size, Unit unit = Unit.Point)
|
||||
public static ILine LineVertical(this IContainer element, float thickness, Unit unit = Unit.Point)
|
||||
{
|
||||
return element.Line(LineType.Vertical, size.ToPoints(unit));
|
||||
return element.Line(LineType.Vertical, thickness.ToPoints(unit));
|
||||
}
|
||||
|
||||
public static ILine LineHorizontal(this IContainer element, float size, Unit unit = Unit.Point)
|
||||
public static ILine LineHorizontal(this IContainer element, float thickness, Unit unit = Unit.Point)
|
||||
{
|
||||
return element.Line(LineType.Horizontal, size.ToPoints(unit));
|
||||
return element.Line(LineType.Horizontal, thickness.ToPoints(unit));
|
||||
}
|
||||
|
||||
public static void LineColor(this ILine descriptor, string value)
|
||||
|
||||
@@ -10,9 +10,7 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
internal Page Page { get; } = new Page();
|
||||
|
||||
#region Size
|
||||
|
||||
public void Size(float width, float height, Unit unit = Unit.Point)
|
||||
public void Size(float width, float height, Unit unit = Unit.Inch)
|
||||
{
|
||||
var pageSize = new PageSize(width, height, unit);
|
||||
|
||||
@@ -41,10 +39,6 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
Page.MaxSize = pageSize;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Margin
|
||||
|
||||
public void MarginLeft(float value, Unit unit = Unit.Point)
|
||||
{
|
||||
@@ -84,10 +78,6 @@ namespace QuestPDF.Fluent
|
||||
MarginHorizontal(value, unit);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
public void DefaultTextStyle(TextStyle textStyle)
|
||||
{
|
||||
Page.DefaultTextStyle = textStyle;
|
||||
@@ -98,16 +88,6 @@ namespace QuestPDF.Fluent
|
||||
DefaultTextStyle(handler(TextStyle.Default));
|
||||
}
|
||||
|
||||
public void ContentFromLeftToRight()
|
||||
{
|
||||
Page.ContentDirection = ContentDirection.LeftToRight;
|
||||
}
|
||||
|
||||
public void ContentFromRightToLeft()
|
||||
{
|
||||
Page.ContentDirection = ContentDirection.RightToLeft;
|
||||
}
|
||||
|
||||
public void PageColor(string color)
|
||||
{
|
||||
Page.BackgroundColor = color;
|
||||
@@ -119,10 +99,6 @@ namespace QuestPDF.Fluent
|
||||
PageColor(color);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Slots
|
||||
|
||||
public IContainer Background()
|
||||
{
|
||||
var container = new Container();
|
||||
@@ -157,8 +133,6 @@ namespace QuestPDF.Fluent
|
||||
Page.Footer = container;
|
||||
return container;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public static class PageExtensions
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Fluent
|
||||
{
|
||||
public static class RepeatContentExtensions
|
||||
{
|
||||
private static IContainer RepeatContent(this IContainer element, Action<RepeatContentSetter> handler)
|
||||
{
|
||||
var repeatContentSetter = element as RepeatContentSetter ?? new RepeatContentSetter();
|
||||
handler(repeatContentSetter);
|
||||
|
||||
return element.Element(repeatContentSetter);
|
||||
}
|
||||
|
||||
public static IContainer RepeatContentWhenPaging(this IContainer element)
|
||||
{
|
||||
return element.RepeatContent(x => x.RepeatContent = true);
|
||||
}
|
||||
|
||||
public static IContainer DoNotRepeatContentWhenPaging(this IContainer element)
|
||||
{
|
||||
return element.RepeatContent(x => x.RepeatContent = false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,7 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
private ICollection<TextBlock> TextBlocks { get; } = new List<TextBlock>();
|
||||
private TextStyle? DefaultStyle { get; set; }
|
||||
internal HorizontalAlignment? Alignment { get; set; }
|
||||
internal HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
|
||||
private float Spacing { get; set; } = 0f;
|
||||
|
||||
public void DefaultTextStyle(TextStyle style)
|
||||
@@ -62,7 +62,7 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
DefaultStyle = style(TextStyle.Default);
|
||||
}
|
||||
|
||||
|
||||
public void AlignLeft()
|
||||
{
|
||||
Alignment = HorizontalAlignment.Left;
|
||||
@@ -242,7 +242,7 @@ namespace QuestPDF.Fluent
|
||||
|
||||
internal void Compose(IContainer container)
|
||||
{
|
||||
TextBlocks.ToList().ForEach(x => x.Alignment ??= Alignment);
|
||||
TextBlocks.ToList().ForEach(x => x.Alignment = Alignment);
|
||||
|
||||
if (DefaultStyle != null)
|
||||
container = container.DefaultTextStyle(DefaultStyle);
|
||||
@@ -276,22 +276,16 @@ namespace QuestPDF.Fluent
|
||||
descriptor.Compose(element);
|
||||
}
|
||||
|
||||
[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.")]
|
||||
[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.")]
|
||||
public static void Text(this IContainer element, object? text, TextStyle 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)
|
||||
{
|
||||
return element.Text(text?.ToString());
|
||||
}
|
||||
|
||||
public static TextSpanDescriptor Text(this IContainer element, string? text)
|
||||
{
|
||||
var descriptor = (TextSpanDescriptor) null!;
|
||||
element.Text(x => descriptor = x.Span(text));
|
||||
var descriptor = (TextSpanDescriptor) null;
|
||||
element.Text(x => descriptor = x.Span(text?.ToString()));
|
||||
return descriptor;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,18 +55,7 @@ namespace QuestPDF.Fluent
|
||||
descriptor.MutateTextStyle(x => x.LineHeight(value));
|
||||
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
|
||||
{
|
||||
descriptor.MutateTextStyle(x => x.Italic(value));
|
||||
@@ -175,27 +164,5 @@ namespace QuestPDF.Fluent
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Direction
|
||||
|
||||
public static T DirectionAuto<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
descriptor.MutateTextStyle(x => x.DirectionAuto());
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T DirectionFromLeftToRight<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
descriptor.MutateTextStyle(x => x.DirectionFromLeftToRight());
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T DirectionFromRightToLeft<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
descriptor.MutateTextStyle(x => x.DirectionFromRightToLeft());
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -48,17 +48,7 @@ namespace QuestPDF.Fluent
|
||||
{
|
||||
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)
|
||||
{
|
||||
return style.Mutate(TextStyleProperty.IsItalic, value);
|
||||
@@ -175,29 +165,5 @@ namespace QuestPDF.Fluent
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Direction
|
||||
|
||||
private static TextStyle TextDirection(this TextStyle style, TextDirection textDirection)
|
||||
{
|
||||
return style.Mutate(TextStyleProperty.Direction, textDirection);
|
||||
}
|
||||
|
||||
public static TextStyle DirectionAuto(this TextStyle style)
|
||||
{
|
||||
return style.TextDirection(Infrastructure.TextDirection.Auto);
|
||||
}
|
||||
|
||||
public static TextStyle DirectionFromLeftToRight(this TextStyle style)
|
||||
{
|
||||
return style.TextDirection(Infrastructure.TextDirection.LeftToRight);
|
||||
}
|
||||
|
||||
public static TextStyle DirectionFromRightToLeft(this TextStyle style)
|
||||
{
|
||||
return style.TextDirection(Infrastructure.TextDirection.RightToLeft);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ namespace QuestPDF.Helpers
|
||||
{
|
||||
public static class Placeholders
|
||||
{
|
||||
public static readonly Random Random = new Random();
|
||||
public static readonly Random Random = new Random(0);
|
||||
|
||||
#region Word Cache
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace QuestPDF.Infrastructure
|
||||
{
|
||||
internal enum ContentDirection
|
||||
{
|
||||
LeftToRight,
|
||||
RightToLeft
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,12 @@ namespace QuestPDF.Infrastructure
|
||||
yield break;
|
||||
}
|
||||
|
||||
internal virtual void Initialize(IPageContext pageContext, ICanvas canvas)
|
||||
{
|
||||
PageContext = pageContext;
|
||||
Canvas = canvas;
|
||||
}
|
||||
|
||||
internal virtual void CreateProxy(Func<Element?, Element?> create)
|
||||
{
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace QuestPDF.Infrastructure
|
||||
{
|
||||
internal interface IContentDirectionAware
|
||||
{
|
||||
public ContentDirection ContentDirection { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace QuestPDF.Infrastructure
|
||||
{
|
||||
internal interface IVisual
|
||||
{
|
||||
public bool IsRendered { get; set; }
|
||||
public bool RepeatContent { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace QuestPDF.Infrastructure
|
||||
{
|
||||
internal enum TextDirection
|
||||
{
|
||||
Auto,
|
||||
LeftToRight,
|
||||
RightToLeft
|
||||
}
|
||||
}
|
||||
@@ -10,14 +10,12 @@ namespace QuestPDF.Infrastructure
|
||||
internal string? FontFamily { get; set; }
|
||||
internal float? Size { get; set; }
|
||||
internal float? LineHeight { get; set; }
|
||||
internal float? LetterSpacing { get; set; }
|
||||
internal FontWeight? FontWeight { get; set; }
|
||||
internal FontPosition? FontPosition { get; set; }
|
||||
internal bool? IsItalic { get; set; }
|
||||
internal bool? HasStrikethrough { get; set; }
|
||||
internal bool? HasUnderline { get; set; }
|
||||
internal bool? WrapAnywhere { get; set; }
|
||||
internal TextDirection? Direction { get; set; }
|
||||
|
||||
internal TextStyle? Fallback { get; set; }
|
||||
|
||||
@@ -28,14 +26,12 @@ namespace QuestPDF.Infrastructure
|
||||
FontFamily = Fonts.Lato,
|
||||
Size = 12,
|
||||
LineHeight = 1.2f,
|
||||
LetterSpacing = 0,
|
||||
FontWeight = Infrastructure.FontWeight.Normal,
|
||||
FontPosition = Infrastructure.FontPosition.Normal,
|
||||
IsItalic = false,
|
||||
HasStrikethrough = false,
|
||||
HasUnderline = false,
|
||||
WrapAnywhere = false,
|
||||
Direction = TextDirection.Auto,
|
||||
Fallback = null
|
||||
};
|
||||
|
||||
|
||||
@@ -11,15 +11,13 @@ namespace QuestPDF.Infrastructure
|
||||
FontFamily,
|
||||
Size,
|
||||
LineHeight,
|
||||
LetterSpacing,
|
||||
FontWeight,
|
||||
FontPosition,
|
||||
IsItalic,
|
||||
HasStrikethrough,
|
||||
HasUnderline,
|
||||
WrapAnywhere,
|
||||
Fallback,
|
||||
Direction
|
||||
Fallback
|
||||
}
|
||||
|
||||
internal static class TextStyleManager
|
||||
@@ -103,19 +101,6 @@ namespace QuestPDF.Infrastructure
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -207,19 +192,6 @@ namespace QuestPDF.Infrastructure
|
||||
|
||||
return origin with { Fallback = castedValue };
|
||||
}
|
||||
|
||||
if (property == TextStyleProperty.Direction)
|
||||
{
|
||||
if (!overrideValue && origin.Direction != null)
|
||||
return origin;
|
||||
|
||||
var castedValue = (TextDirection?)value;
|
||||
|
||||
if (origin.Direction == castedValue)
|
||||
return origin;
|
||||
|
||||
return origin with { Direction = castedValue };
|
||||
}
|
||||
|
||||
throw new ArgumentOutOfRangeException(nameof(property), property, "Expected to mutate the TextStyle object. Provided property type is not supported.");
|
||||
}
|
||||
@@ -260,14 +232,12 @@ namespace QuestPDF.Infrastructure
|
||||
result = MutateStyle(result, TextStyleProperty.FontFamily, parent.FontFamily, overrideStyle);
|
||||
result = MutateStyle(result, TextStyleProperty.Size, parent.Size, overrideStyle);
|
||||
result = MutateStyle(result, TextStyleProperty.LineHeight, parent.LineHeight, overrideStyle);
|
||||
result = MutateStyle(result, TextStyleProperty.LetterSpacing, parent.LetterSpacing, overrideStyle);
|
||||
result = MutateStyle(result, TextStyleProperty.FontWeight, parent.FontWeight, overrideStyle);
|
||||
result = MutateStyle(result, TextStyleProperty.FontPosition, parent.FontPosition, overrideStyle);
|
||||
result = MutateStyle(result, TextStyleProperty.IsItalic, parent.IsItalic, overrideStyle);
|
||||
result = MutateStyle(result, TextStyleProperty.HasStrikethrough, parent.HasStrikethrough, overrideStyle);
|
||||
result = MutateStyle(result, TextStyleProperty.HasUnderline, parent.HasUnderline, overrideStyle);
|
||||
result = MutateStyle(result, TextStyleProperty.WrapAnywhere, parent.WrapAnywhere, overrideStyle);
|
||||
result = MutateStyle(result, TextStyleProperty.Direction, parent.Direction, overrideStyle);
|
||||
|
||||
if (applyFallback)
|
||||
result = MutateStyle(result, TextStyleProperty.Fallback, parent.Fallback, overrideStyle);
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace QuestPDF.Previewer
|
||||
public event Action? OnPreviewerStopped;
|
||||
|
||||
private const int RequiredPreviewerVersionMajor = 2022;
|
||||
private const int RequiredPreviewerVersionMinor = 12;
|
||||
private const int RequiredPreviewerVersionMinor = 9;
|
||||
|
||||
public PreviewerService(int port)
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<Authors>MarcinZiabek</Authors>
|
||||
<Company>CodeFlint</Company>
|
||||
<PackageId>QuestPDF</PackageId>
|
||||
<Version>2022.12.0</Version>
|
||||
<Version>2022.9.1</Version>
|
||||
<PackageDescription>QuestPDF is an open-source, modern and battle-tested library that can help you with generating PDF documents by offering friendly, discoverable and predictable C# fluent API.</PackageDescription>
|
||||
<PackageReleaseNotes>$([System.IO.File]::ReadAllText("$(MSBuildProjectDirectory)/Resources/ReleaseNotes.txt"))</PackageReleaseNotes>
|
||||
<LangVersion>9</LangVersion>
|
||||
@@ -11,7 +11,6 @@
|
||||
<PackageIcon>Logo.png</PackageIcon>
|
||||
<PackageIconUrl>https://www.questpdf.com/images/package-logo.png</PackageIconUrl>
|
||||
<PackageProjectUrl>https://www.questpdf.com/</PackageProjectUrl>
|
||||
<PackageReadmeFile>PackageReadme.md</PackageReadmeFile>
|
||||
<RepositoryUrl>https://github.com/QuestPDF/library.git</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<Copyright>Marcin Ziąbek, QuestPDF contributors</Copyright>
|
||||
@@ -24,8 +23,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SkiaSharp" Version="2.88.3" />
|
||||
<PackageReference Include="SkiaSharp.HarfBuzz" Version="2.88.3" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.80.4" />
|
||||
<PackageReference Include="SkiaSharp.HarfBuzz" Version="2.80.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -40,10 +39,11 @@
|
||||
<EmbeddedResource Include="Resources\ImagePlaceholder.png" />
|
||||
<EmbeddedResource Include="Resources\Logo.png" />
|
||||
<None Remove="ImagePlaceholder.png" />
|
||||
|
||||
<None Include="Resources\Logo.png" Pack="true" PackagePath="\" />
|
||||
<None Include="Resources\PackageReadme.md" Pack="true" PackagePath="\" />
|
||||
|
||||
<None Include="Resources\Logo.png">
|
||||
<Pack>true</Pack>
|
||||
<Visible>false</Visible>
|
||||
<PackagePath>\</PackagePath>
|
||||
</None>
|
||||
<None Remove="Resources\DefaultFont\Lato-Black.ttf" />
|
||||
<EmbeddedResource Include="Resources\DefaultFont\Lato-Black.ttf" />
|
||||
<None Remove="Resources\DefaultFont\Lato-BlackItalic.ttf" />
|
||||
|
||||
@@ -21,7 +21,7 @@ Install-Package QuestPDF
|
||||
dotnet add package QuestPDF
|
||||
|
||||
// Package reference in .csproj file
|
||||
<PackageReference Include="QuestPDF" Version="2022.12.0" />
|
||||
<PackageReference Include="QuestPDF" Version="2022.3.0" />
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
Feature: implemented LetterSpacing property for the Text element
|
||||
Improvement: the Text element API accepts now only string values, objects are not automatically converted anymore
|
||||
Fix: the Alignment element incorrectly limits size of its child when only one axis is set (horizontal or vertical)
|
||||
Maintenance: Updated SkiaSharp dependency to 2.88.3
|
||||
2022.9.0
|
||||
- Implemented font-fallback algorithm,
|
||||
- Introduced new Settings API,
|
||||
- Significantly reduced memory allocation cost for TextStyle objects,
|
||||
- Implemented optional checking if all font glyphs are available,
|
||||
- Minor text-rendering optimizations.
|
||||
|
||||
2022.9.1
|
||||
- Fixed: text hyperlinks do not work when the CheckIfAllTextGlyphsAreAvailable option or text fallback are used,
|
||||
- Fixed: cells with RowSpan (greater than 1) are not always displayed properly,
|
||||
- Improved predictability of the Inlined element when measuring its children.
|
||||
@@ -23,7 +23,7 @@ Choosing a project dependency could be difficult. We need to ensure stability an
|
||||
|
||||
⭐ Please give this repository a star. It takes seconds and help thousands of developers! ⭐
|
||||
|
||||
<img src="https://user-images.githubusercontent.com/9263853/207596765-f3b84375-e4da-4597-b6a4-f2e22dd2b479.png" width="800" />
|
||||
<img src="https://user-images.githubusercontent.com/9263853/190931857-8ca52ec8-cc7d-4d12-9467-4442b3342fa1.png" width="700" />
|
||||
|
||||
|
||||
## Please share with the community
|
||||
@@ -63,7 +63,7 @@ Install-Package QuestPDF
|
||||
dotnet add package QuestPDF
|
||||
|
||||
// Package reference in .csproj file
|
||||
<PackageReference Include="QuestPDF" Version="2022.12.0" />
|
||||
<PackageReference Include="QuestPDF" Version="2022.9.0" />
|
||||
```
|
||||
|
||||
[](https://www.nuget.org/packages/QuestPDF/)
|
||||
|
||||
Reference in New Issue
Block a user