Compare commits

..

1 Commits

Author SHA1 Message Date
MarcinZiabek d39819bf8d Optimized drawing border 2021-12-26 21:32:49 +01:00
144 changed files with 2091 additions and 4567 deletions
-70
View File
@@ -1,70 +0,0 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ main ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ main ]
schedule:
- cron: '22 16 * * 5'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'csharp' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://git.io/codeql-language-support
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
+32
View File
@@ -0,0 +1,32 @@
using System.IO;
using NUnit.Framework;
using QuestPDF.Drawing;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class BarCode
{
[Test]
public void Example()
{
FontManager.RegisterFontType("LibreBarcode39", File.OpenRead("LibreBarcode39-Regular.ttf"));
RenderingTest
.Create()
.PageSize(400, 100)
.ShowResults()
.Render(container =>
{
container
.Background(Colors.White)
.AlignCenter()
.AlignMiddle()
.Text("*QuestPDF*", TextStyle.Default.FontType("LibreBarcode39").Size(64));
});
}
}
}
+12 -13
View File
@@ -1,6 +1,4 @@
using System.IO;
using NUnit.Framework;
using QuestPDF.Drawing;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
@@ -8,25 +6,26 @@ using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
[TestFixture]
public class BarcodeExamples
{
[Test]
public void Example()
public void Barcode()
{
FontManager.RegisterFontType(File.OpenRead("LibreBarcode39-Regular.ttf"));
RenderingTest
.Create()
.PageSize(400, 200)
.ShowResults()
.PageSize(300, 300)
.Render(container =>
{
container
.Background(Colors.White)
.AlignCenter()
.AlignMiddle()
.Text("*QuestPDF*", TextStyle.Default.FontType("Libre Barcode 39").Size(64));
.Background("#FFF")
.Padding(25)
.Stack(stack =>
{
stack.Item().Border(1).Background(Colors.Grey.Lighten3).Padding(5).Text("Barcode Example");
stack.Item().Border(1).Padding(5).AlignCenter().Text("*123456789*", TextStyle.Default.FontType("CarolinaBar-Demo-25E2").Size(20));
});
});
}
}
}
}
-57
View File
@@ -1,57 +0,0 @@
using Microcharts;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using SkiaSharp;
namespace QuestPDF.Examples
{
public class CanvasExamples
{
[Test]
public void BorderRadius()
{
RenderingTest
.Create()
.PageSize(175, 100)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Background(Colors.Grey.Lighten2)
.Padding(25)
.MinimalBox()
.Layers(layers =>
{
layers.Layer().Canvas((canvas, size) =>
{
DrawRoundedRectangle(Colors.White, false);
DrawRoundedRectangle(Colors.Blue.Darken2, true);
void DrawRoundedRectangle(string color, bool isStroke)
{
using var paint = new SKPaint
{
Color = SKColor.Parse(color),
IsStroke = isStroke,
StrokeWidth = 2,
IsAntialias = true
};
canvas.DrawRoundRect(0, 0, size.Width, size.Height, 20, 20, paint);
}
});
layers
.PrimaryLayer()
.PaddingVertical(10)
.PaddingHorizontal(20)
.Text("Sample text", TextStyle.Default.Size(16).Color(Colors.Blue.Darken2).SemiBold());
});
});
}
}
}
+3 -3
View File
@@ -52,14 +52,14 @@ namespace QuestPDF.Examples
container
.Background(Colors.White)
.Padding(25)
.Column(column =>
.Stack(stack =>
{
column
stack
.Item()
.PaddingBottom(10)
.Text("Chart example", TextStyle.Default.Size(20).SemiBold().Color(Colors.Blue.Medium));
column
stack
.Item()
.Border(1)
.ExtendHorizontal()
-56
View File
@@ -1,56 +0,0 @@
using System;
using System.Linq;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class ColumnExamples
{
[Test]
public void Column()
{
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.ShowResults()
.ProducePdf()
.Render(container =>
{
container.Column(column =>
{
foreach (var i in Enumerable.Range(0, 10))
column.Item().Element(Block);
static void Block(IContainer container)
{
container
.Width(72)
.Height(3.5f, Unit.Inch)
.Height(1.5f, Unit.Inch)
.Background(Placeholders.BackgroundColor());
}
});
});
}
[Test]
public void Stability_NoItems()
{
RenderingTest
.Create()
.ProducePdf()
.MaxPages(100)
.PageSize(250, 150)
.Render(container =>
{
container
.Padding(25)
.Column(column => { });
});
}
}
}
+5 -5
View File
@@ -36,8 +36,8 @@ namespace QuestPDF.Examples
.BorderColor(Colors.Black)
.Row(row =>
{
row.RelativeItem().Element(x => GenerateStructure(x, level));
row.RelativeItem().Element(x => GenerateStructure(x, level));
row.RelativeColumn().Element(x => GenerateStructure(x, level));
row.RelativeColumn().Element(x => GenerateStructure(x, level));
});
}
else
@@ -45,10 +45,10 @@ namespace QuestPDF.Examples
container
.Border(level / 4f)
.BorderColor(Colors.Black)
.Column(column =>
.Stack(stack =>
{
column.Item().Element(x => GenerateStructure(x, level));
column.Item().Element(x => GenerateStructure(x, level));
stack.Item().Element(x => GenerateStructure(x, level));
stack.Item().Element(x => GenerateStructure(x, level));
});
}
}
+2 -2
View File
@@ -22,10 +22,10 @@ namespace QuestPDF.Examples
page.Header().Text("Header");
page.Content().PaddingVertical(10).Border(1).Padding(10).Column(column =>
page.Content().PaddingVertical(10).Border(1).Padding(10).Stack(stack =>
{
foreach (var index in Enumerable.Range(1, 100))
column.Item().Text($"Line {index}", TextStyle.Default.Color(Placeholders.Color()));
stack.Item().Text($"Line {index}", TextStyle.Default.Color(Placeholders.Color()));
});
page.Footer().Text("Footer");
+2 -2
View File
@@ -9,7 +9,7 @@ namespace QuestPDF.Examples
public class DebuggingTesting
{
[Test]
public void Column()
public void Stack()
{
Assert.Throws<DocumentLayoutException>(() =>
{
@@ -23,7 +23,7 @@ namespace QuestPDF.Examples
.Width(100)
.Background(Colors.Grey.Lighten3)
.DebugPointer("Example debug pointer")
.Column(x =>
.Stack(x =>
{
x.Item().Text("Test");
x.Item().Width(150);
+4 -4
View File
@@ -13,7 +13,7 @@ namespace QuestPDF.Examples
{
RenderingTest
.Create()
.ProducePdf()
.ProduceImages()
.ShowResults()
.RenderDocument(container =>
{
@@ -26,11 +26,11 @@ namespace QuestPDF.Examples
page.Size(PageSizes.A4);
page.Background(Colors.White);
page.Content().Column(column =>
page.Content().Stack(stack =>
{
column.Item().Text(Placeholders.Sentence());
stack.Item().Text(Placeholders.Sentence());
column.Item().Text(text =>
stack.Item().Text(text =>
{
// text in this block is additionally semibold
text.DefaultTextStyle(TextStyle.Default.SemiBold());
@@ -1,53 +0,0 @@
using System.Drawing;
using System.Linq;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class DefaultTextStyleExamples
{
[Test]
public void Placeholder()
{
RenderingTest
.Create()
.PageSize(220, 270)
.ProduceImages()
.ShowResults()
.EnableDebugging()
.Render(container =>
{
container
.Padding(10)
.DefaultTextStyle(TextStyle.Default.Bold().Underline())
.Column(column =>
{
column.Item().Text("Default style applies to all children", TextStyle.Default);
column.Item().Text("You can override certain styles", TextStyle.Default.Underline(false).Color(Colors.Green.Darken2));
column.Item().PaddingTop(10).Border(1).Grid(grid =>
{
grid.Columns(4);
foreach (var i in Enumerable.Range(1, 16))
{
grid.Item()
.Border(1)
.BorderColor(Colors.Grey.Lighten1)
.Background(Colors.Grey.Lighten3)
.Width(50)
.Height(50)
.AlignCenter()
.AlignMiddle()
.Text(i, TextStyle.Default.Size(16 + i / 4));
}
});
});
});
}
}
}
@@ -1,55 +0,0 @@
using System.Linq;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class DifferentHeaderOnFirstPageExample
{
[Test]
public void Placeholder()
{
RenderingTest
.Create()
.PageSize(PageSizes.A6)
.ProduceImages()
.ShowResults()
.EnableDebugging()
.RenderDocument(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A6);
page.Margin(5);
page.Background(Colors.White);
page.Header().Column(column =>
{
column.Item().ShowOnce().Background(Colors.Blue.Lighten2).Height(60);
column.Item().SkipOnce().Background(Colors.Green.Lighten2).Height(40);
});
page.Content().PaddingVertical(10).Column(column =>
{
column.Spacing(10);
foreach (var _ in Enumerable.Range(0, 13))
column.Item().Background(Colors.Grey.Lighten2).Height(40);
});
page.Footer().AlignCenter().Text(text =>
{
text.DefaultTextStyle(TextStyle.Default.Size(16));
text.CurrentPageNumber();
text.Span(" / ");
text.TotalPages();
});
});
});
}
}
}
+50 -50
View File
@@ -40,7 +40,7 @@ namespace QuestPDF.Examples
.Decoration(decoration =>
{
decoration
.Before()
.Header()
.Background(Colors.Grey.Medium)
.Padding(10)
.Text("Notes", TextStyle.Default.Size(16).Color("#FFF"));
@@ -66,27 +66,27 @@ namespace QuestPDF.Examples
container
.Background("#FFF")
.Padding(20)
.Column(column =>
.Stack(stack =>
{
column.Item()
stack.Item()
.PaddingBottom(10)
.AlignCenter()
.Text("This Row element is 700pt wide");
column.Item().Row(row =>
stack.Item().Row(row =>
{
row.ConstantItem(100)
row.ConstantColumn(100)
.Background(Colors.Grey.Lighten1)
.Padding(10)
.ExtendVertical()
.Text("This column is 100 pt wide");
row.RelativeItem()
row.RelativeColumn()
.Background(Colors.Grey.Lighten2)
.Padding(10)
.Text("This column takes 1/3 of the available space (200pt)");
row.RelativeItem(2)
row.RelativeColumn(2)
.Background(Colors.Grey.Lighten3)
.Padding(10)
.Text("This column takes 2/3 of the available space (400pt)");
@@ -109,15 +109,15 @@ namespace QuestPDF.Examples
.Row(row =>
{
row.Spacing(20);
row.RelativeItem(2).Border(1).Background(Colors.Grey.Lighten1);
row.RelativeItem(3).Border(1).Background(Colors.Grey.Lighten2);
row.RelativeItem(4).Border(1).Background(Colors.Grey.Lighten3);
row.RelativeColumn(2).Border(1).Background(Colors.Grey.Lighten1);
row.RelativeColumn(3).Border(1).Background(Colors.Grey.Lighten2);
row.RelativeColumn(4).Border(1).Background(Colors.Grey.Lighten3);
});
});
}
[Test]
public void column()
public void Stack()
{
RenderingTest
.Create()
@@ -127,13 +127,13 @@ namespace QuestPDF.Examples
container
.Background("#FFF")
.Padding(15)
.Column(column =>
.Stack(stack =>
{
column.Spacing(15);
stack.Spacing(15);
column.Item().Background(Colors.Grey.Medium).Height(50);
column.Item().Background(Colors.Grey.Lighten1).Height(100);
column.Item().Background(Colors.Grey.Lighten2).Height(150);
stack.Item().Background(Colors.Grey.Medium).Height(50);
stack.Item().Background(Colors.Grey.Lighten1).Height(100);
stack.Item().Background(Colors.Grey.Lighten2).Height(150);
});
});
}
@@ -267,12 +267,12 @@ namespace QuestPDF.Examples
layers
.PrimaryLayer()
.Padding(25)
.Column(column =>
.Stack(stack =>
{
column.Spacing(5);
stack.Spacing(5);
foreach (var _ in Enumerable.Range(0, 7))
column.Item().Text(Placeholders.Sentence());
stack.Item().Text(Placeholders.Sentence());
});
// layer above the main content
@@ -304,16 +304,16 @@ namespace QuestPDF.Examples
// {
// page.Header().PageNumber("Page {pdf:currentPage}");
//
// page.Content().Height(300).column(content =>
// page.Content().Height(300).Stack(content =>
// {
// content.Item().Height(200).Background(Colors.Grey.Lighten2);
//
// content.Item().EnsureSpace(100).column(column =>
// content.Item().EnsureSpace(100).Stack(stack =>
// {
// column.Spacing(10);
// stack.Spacing(10);
//
// foreach (var _ in Enumerable.Range(0, 4))
// column.Item().Height(50).Background(Colors.Green.Lighten1);
// stack.Item().Height(50).Background(Colors.Green.Lighten1);
// });
// });
// });
@@ -378,7 +378,7 @@ namespace QuestPDF.Examples
.Row(row =>
{
foreach (var color in colors)
row.RelativeItem().Background(color);
row.RelativeColumn().Background(color);
});
});
}
@@ -437,10 +437,10 @@ namespace QuestPDF.Examples
{
layers.Layer().Text("Something else");
layers.PrimaryLayer().Column(column =>
layers.PrimaryLayer().Stack(stack =>
{
column.Item().PaddingTop(20).Text("Text 1");
column.Item().PaddingTop(40).Text("Text 2");
stack.Item().PaddingTop(20).Text("Text 1");
stack.Item().PaddingTop(40).Text("Text 2");
});
layers.Layer().Canvas((canvas, size) =>
@@ -474,7 +474,7 @@ namespace QuestPDF.Examples
.Padding(15)
.Border(4)
.BorderColor(Colors.Blue.Medium)
//.MinimalBox()
//.Box()
.Background(Colors.Grey.Lighten2)
.Padding(15)
.Text("Test of the \n box element", TextStyle.Default.Size(20));
@@ -501,13 +501,13 @@ namespace QuestPDF.Examples
.SemiBold();
decoration
.Before()
.Header()
.PaddingBottom(10)
.Text("Example: scale component", headerFontStyle);
decoration
.Content()
.Column(column =>
.Stack(stack =>
{
var scales = new[] { 0.8f, 0.9f, 1.1f, 1.2f };
@@ -519,7 +519,7 @@ namespace QuestPDF.Examples
var fontStyle = TextStyle.Default.Size(16);
column
stack
.Item()
.Border(1)
.Background(fontColor)
@@ -542,7 +542,7 @@ namespace QuestPDF.Examples
{
container
.Background("#FFF")
.MinimalBox()
.Box()
.Padding(25)
@@ -588,7 +588,7 @@ namespace QuestPDF.Examples
return element;
})
.MinimalBox()
.Box()
.Background(Colors.White)
.Padding(10)
.Text($"Rotated {turns * 90}°", TextStyle.Default.Size(16));
@@ -686,7 +686,7 @@ namespace QuestPDF.Examples
return element;
})
.MinimalBox()
.Box()
.Background(Colors.White)
.Padding(10)
.Text($"Flipped {turns}", TextStyle.Default.Size(16));
@@ -708,14 +708,14 @@ namespace QuestPDF.Examples
.Border(2)
.Row(row =>
{
row.ConstantItem(25)
row.ConstantColumn(25)
.Border(1)
.RotateLeft()
.AlignCenter()
.AlignMiddle()
.Text("Sample text");
row.RelativeItem().Border(1).Padding(5).Text(Placeholders.Paragraph());
row.RelativeColumn().Border(1).Padding(5).Text(Placeholders.Paragraph());
});
});
}
@@ -731,11 +731,11 @@ namespace QuestPDF.Examples
container
.Padding(25)
.PaddingLeft(75)
.Column(column =>
.Stack(stack =>
{
column.Item().Width(300).Height(150).Background(Colors.Blue.Lighten4);
stack.Item().Width(300).Height(150).Background(Colors.Blue.Lighten4);
column
stack
.Item()
// creates an infinite space for its child
@@ -751,7 +751,7 @@ namespace QuestPDF.Examples
.Background(Colors.Blue.Darken1);
column.Item().Width(300).Height(150).Background(Colors.Blue.Lighten3);
stack.Item().Width(300).Height(150).Background(Colors.Blue.Lighten3);
});
});
}
@@ -766,13 +766,13 @@ namespace QuestPDF.Examples
{
container
.Padding(25)
.Column(column =>
.Stack(stack =>
{
column.Item().Row(row =>
stack.Item().Row(row =>
{
row.RelativeItem().LabelCell("Label 1");
row.RelativeColumn().LabelCell("Label 1");
row.RelativeItem(3).Grid(grid =>
row.RelativeColumn(3).Grid(grid =>
{
grid.Columns(3);
@@ -784,11 +784,11 @@ namespace QuestPDF.Examples
});
});
column.Item().Row(row =>
stack.Item().Row(row =>
{
row.RelativeItem().ValueCell().Text("Value 1");
row.RelativeColumn().ValueCell().Text("Value 1");
row.RelativeItem(3).Grid(grid =>
row.RelativeColumn(3).Grid(grid =>
{
grid.Columns(3);
@@ -800,10 +800,10 @@ namespace QuestPDF.Examples
});
});
column.Item().Row(row =>
stack.Item().Row(row =>
{
row.RelativeItem().LabelCell("Label 6");
row.RelativeItem().ValueCell().Text("Value 6");
row.RelativeColumn().LabelCell("Label 6");
row.RelativeColumn().ValueCell().Text("Value 6");
});
});
});
+6 -21
View File
@@ -20,8 +20,6 @@ namespace QuestPDF.Examples.Engine
private Size Size { get; set; }
private int? MaxPagesThreshold { get; set; }
private bool ShowResult { get; set; }
private bool ApplyCaching { get; set; }
private bool ApplyDebugging { get; set; }
private RenderingTestResult ResultType { get; set; } = RenderingTestResult.Images;
private RenderingTest()
@@ -69,24 +67,6 @@ namespace QuestPDF.Examples.Engine
return this;
}
public RenderingTest MaxPages(int value)
{
MaxPagesThreshold = value;
return this;
}
public RenderingTest EnableCaching(bool value = true)
{
ApplyCaching = value;
return this;
}
public RenderingTest EnableDebugging(bool value = true)
{
ApplyDebugging = value;
return this;
}
public void Render(Action<IContainer> content)
{
RenderDocument(container =>
@@ -98,11 +78,16 @@ namespace QuestPDF.Examples.Engine
});
});
}
public void MaxPages(int value)
{
MaxPagesThreshold = value;
}
public void RenderDocument(Action<IDocumentContainer> content)
{
MaxPagesThreshold ??= ResultType == RenderingTestResult.Pdf ? 1000 : 10;
var document = new SimpleDocument(content, MaxPagesThreshold.Value, ApplyCaching, ApplyDebugging);
var document = new SimpleDocument(content, MaxPagesThreshold.Value);
Render(document);
}
+2 -8
View File
@@ -13,15 +13,11 @@ namespace QuestPDF.Examples.Engine
private Action<IDocumentContainer> Content { get; }
private int MaxPages { get; }
private bool ApplyCaching { get; }
private bool ApplyDebugging { get; }
public SimpleDocument(Action<IDocumentContainer> content, int maxPages, bool applyCaching, bool applyDebugging)
public SimpleDocument(Action<IDocumentContainer> content, int maxPages)
{
Content = content;
MaxPages = maxPages;
ApplyCaching = applyCaching;
ApplyDebugging = applyDebugging;
}
public DocumentMetadata GetMetadata()
@@ -29,9 +25,7 @@ namespace QuestPDF.Examples.Engine
return new DocumentMetadata()
{
RasterDpi = PageSizes.PointsPerInch * ImageScalingFactor,
DocumentLayoutExceptionThreshold = MaxPages,
ApplyCaching = ApplyCaching,
ApplyDebugging = ApplyDebugging
DocumentLayoutExceptionThreshold = MaxPages
};
}
+3 -3
View File
@@ -25,15 +25,15 @@ namespace QuestPDF.Examples
page.Header().Text("With ensure space", TextStyle.Default.SemiBold());
page.Content().Column(column =>
page.Content().Stack(stack =>
{
column
stack
.Item()
.ExtendHorizontal()
.Height(75)
.Background(Colors.Grey.Lighten2);
column
stack
.Item()
.EnsureSpace(100)
.Text(Placeholders.LoremIpsum());
@@ -1,76 +0,0 @@
using NUnit.Framework;
using QuestPDF.Drawing.Exceptions;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class ExecutionOrderExamples
{
[Test]
public void Example()
{
RenderingTest
.Create()
.PageSize(400, 300)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.DefaultTextStyle(TextStyle.Default.Size(18))
.Padding(25)
.Row(row =>
{
row.Spacing(25);
row.RelativeItem()
.Border(1)
.Padding(15)
.Background(Colors.Grey.Lighten2)
.Text("Lorem ipsum");
row.RelativeItem()
.Border(1)
.Background(Colors.Grey.Lighten2)
.Padding(15)
.Text("dolor sit amet");
});
});
}
[Test]
public void Example2()
{
RenderingTest
.Create()
.PageSize(200, 200)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(25)
.Border(2)
.Width(150)
.Height(150)
.Background(Colors.Blue.Lighten2)
.PaddingTop(50)
.Background(Colors.Green.Lighten2)
.PaddingRight(50)
.Background(Colors.Red.Lighten2)
.PaddingBottom(50)
.Background(Colors.Amber.Lighten2)
.PaddingLeft(50)
.Background(Colors.Grey.Lighten2);
});
}
}
}
+8 -10
View File
@@ -12,14 +12,12 @@ namespace QuestPDF.Examples
{
return container
.Border(1)
.BorderColor(Colors.Grey.Medium)
.Background(dark ? Colors.Grey.Lighten3 : Colors.White)
.Padding(5);
.Background(dark ? Colors.Grey.Lighten2 : Colors.White)
.Padding(10);
}
public static void LabelCell(this IContainer container, string text) => container.Cell(true).Text(text, TextStyle.Default.SemiBold());
public static void LabelCell(this IContainer container, string text) => container.Cell(true).Text(text, TextStyle.Default.Medium());
public static IContainer ValueCell(this IContainer container) => container.Cell(false);
public static void ValueCell(this IContainer container, string text) => container.ValueCell().Text(text, TextStyle.Default);
}
public class FrameExample
@@ -36,14 +34,14 @@ namespace QuestPDF.Examples
container
.Background("#FFF")
.Padding(25)
.Column(column =>
.Stack(stack =>
{
for(var i = 1; i <= 4; i++)
for(var i=1; i<=4; i++)
{
column.Item().Row(row =>
stack.Item().Row(row =>
{
row.RelativeItem(2).LabelCell(Placeholders.Label());
row.RelativeItem(3).ValueCell().Text(Placeholders.Paragraph());
row.RelativeColumn(2).LabelCell(Placeholders.Label());
row.RelativeColumn(3).ValueCell().Text(Placeholders.Paragraph());
});
}
});
+5 -5
View File
@@ -19,17 +19,17 @@ namespace QuestPDF.Examples
.ShowResults()
.Render(page =>
{
page.Padding(25).Column(column =>
page.Padding(25).Stack(stack =>
{
column.Spacing(25);
stack.Spacing(25);
column.Item().Image("logo.png");
stack.Item().Image("logo.png");
var binaryData = File.ReadAllBytes("logo.png");
column.Item().Image(binaryData);
stack.Item().Image(binaryData);
using var stream = new FileStream("logo.png", FileMode.Open);
column.Item().Image(stream);
stack.Item().Image(stream);
});
});
}
+3 -3
View File
@@ -25,7 +25,7 @@ namespace QuestPDF.Examples
.Padding(25)
.Decoration(decoration =>
{
decoration.Before().Text(text =>
decoration.Header().Text(text =>
{
text.DefaultTextStyle(TextStyle.Default.Size(20));
@@ -37,7 +37,7 @@ namespace QuestPDF.Examples
decoration
.Content()
.PaddingTop(25)
//.MinimalBox()
//.Box()
.Border(1)
.Background(Colors.Grey.Lighten2)
.Inlined(inlined =>
@@ -94,7 +94,7 @@ namespace QuestPDF.Examples
{
container
.Padding(20)
.MinimalBox()
.Box()
.Border(1)
.Background(Colors.Grey.Lighten4)
.Inlined(inlined =>
-57
View File
@@ -1,57 +0,0 @@
using System.IO;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class LineExamples
{
[Test]
public void LineHorizontal()
{
RenderingTest
.Create()
.PageSize(PageSizes.A5)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(15)
.MinimalBox()
.DefaultTextStyle(TextStyle.Default.Size(16))
.Column(column =>
{
column.Item().Text("Above text");
column.Item().PaddingVertical(5).LineHorizontal(1).LineColor(Colors.Grey.Medium);
column.Item().Text("Below text");
});
});
}
[Test]
public void LineVertical()
{
RenderingTest
.Create()
.PageSize(PageSizes.A5)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(15)
.DefaultTextStyle(TextStyle.Default.Size(16))
.Row(row =>
{
row.AutoItem().Text("Left text");
row.AutoItem().PaddingHorizontal(10).LineVertical(1).LineColor(Colors.Grey.Medium);
row.AutoItem().Text("Right text");
});
});
}
}
}
+1 -1
View File
@@ -43,7 +43,7 @@ namespace QuestPDF.Examples
container
.Background("#FFF")
.Padding(25)
.Column(column =>
.Stack(column =>
{
column.Spacing(10);
-44
View File
@@ -1,44 +0,0 @@
using System.Diagnostics;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using ShimSkiaSharp;
using SKTypeface = SkiaSharp.SKTypeface;
namespace QuestPDF.Examples
{
public class MinimalApiExamples
{
[Test]
public void MinimalApi()
{
Document
.Create(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A5);
page.Margin(1.5f, Unit.Centimetre);
page.Header()
.Text("Hello PDF!", TextStyle.Default.SemiBold().Size(20));
page.Content()
.PaddingVertical(1, Unit.Centimetre)
.Column(x =>
{
x.Spacing(10);
x.Item().Text(Placeholders.LoremIpsum());
x.Item().Image(Placeholders.Image(200, 100));
});
});
})
.GeneratePdf("hello.pdf");
Process.Start("explorer.exe", "hello.pdf");
}
}
}
+6 -6
View File
@@ -63,7 +63,7 @@ namespace QuestPDF.Examples
.Render(container =>
{
container
.Column(column =>
.Stack(column =>
{
column
.Item()
@@ -113,14 +113,14 @@ namespace QuestPDF.Examples
.Render(container =>
{
container
.Column(column =>
.Stack(column =>
{
column
.Item()
.Height(150)
.Row(row =>
{
row.RelativeItem()
row.RelativeColumn()
.Extend()
.Background("FFF")
@@ -128,7 +128,7 @@ namespace QuestPDF.Examples
.Width(50)
.Background("444");
row.RelativeItem()
row.RelativeColumn()
.Extend()
.Background("BBB")
@@ -142,7 +142,7 @@ namespace QuestPDF.Examples
.Height(150)
.Row(row =>
{
row.RelativeItem()
row.RelativeColumn()
.Extend()
.Background("BBB")
@@ -150,7 +150,7 @@ namespace QuestPDF.Examples
.Width(50)
.Background("444");
row.RelativeItem()
row.RelativeColumn()
.Extend()
.Background("BBB")
@@ -6,13 +6,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
<PackageReference Include="microcharts" Version="0.9.5.9" />
<PackageReference Include="nunit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="SkiaSharp" Version="2.80.3" />
<PackageReference Include="Svg.Skia" Version="0.5.10" />
</ItemGroup>
<ItemGroup>
@@ -29,9 +27,6 @@
<None Update="logo.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="pdf-icon.svg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
-109
View File
@@ -1,109 +0,0 @@
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class RowExamples
{
[Test]
public void ItemTypes()
{
RenderingTest
.Create()
.ProducePdf()
.PageSize(650, 300)
.ShowResults()
.Render(container =>
{
container
.Padding(25)
.MinimalBox()
.Border(1)
.Column(column =>
{
column.Item().LabelCell("Total width: 600px");
column.Item().Row(row =>
{
row.ConstantItem(150).ValueCell("150px");
row.ConstantItem(100).ValueCell("100px");
row.RelativeItem(4).ValueCell("200px");
row.RelativeItem(3).ValueCell("150px");
});
column.Item().Row(row =>
{
row.ConstantItem(100).ValueCell("100px");
row.ConstantItem(50).ValueCell("50px");
row.RelativeItem(2).ValueCell("100px");
row.RelativeItem(1).ValueCell("50px");
});
});
});
}
[Test]
public void Stability()
{
// up to version 2021.12, this code would always result with the infinite layout exception
RenderingTest
.Create()
.ProducePdf()
.MaxPages(100)
.PageSize(250, 150)
.ShowResults()
.Render(container =>
{
container
.Padding(25)
.Row(row =>
{
row.RelativeItem().Column(column =>
{
column.Item().ShowOnce().Element(CreateBox).Text("X");
column.Item().Element(CreateBox).Text("1");
column.Item().Element(CreateBox).Text("2");
});
row.RelativeItem().Column(column =>
{
column.Item().Element(CreateBox).Text("1");
column.Item().Element(CreateBox).Text("2");
});
});
});
static IContainer CreateBox(IContainer container)
{
return container
.ExtendHorizontal()
.ExtendVertical()
.Background(Colors.Grey.Lighten4)
.Border(1)
.AlignCenter()
.AlignMiddle()
.ShowOnce();
}
}
[Test]
public void Stability_NoItems()
{
RenderingTest
.Create()
.ProducePdf()
.MaxPages(100)
.PageSize(250, 150)
.Render(container =>
{
container
.Padding(25)
.Row(row => { });
});
}
}
}
-42
View File
@@ -1,42 +0,0 @@
using System.Linq;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class ScaleToFitExamples
{
[Test]
public void ScaleToFit()
{
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container.Padding(25).Column(column =>
{
var text = Placeholders.Paragraph();
foreach (var i in Enumerable.Range(2, 5))
{
column
.Item()
.MinimalBox()
.Border(1)
.Padding(5)
.Width(i * 40)
.Height(i * 20)
.ScaleToFit()
.Text(text);
}
});
});
}
}
}
+2 -2
View File
@@ -27,14 +27,14 @@ namespace QuestPDF.Examples
page.Content().PaddingVertical(5).Row(row =>
{
row.RelativeItem()
row.RelativeColumn()
.Background(Colors.Grey.Lighten2)
.Border(1)
.Padding(5)
.ShowOnce()
.Text(Placeholders.Label());
row.RelativeItem(2)
row.RelativeColumn(2)
.Border(1)
.Padding(5)
.Text(Placeholders.Paragraph());
+3 -3
View File
@@ -23,10 +23,10 @@ namespace QuestPDF.Examples
page.Size(PageSizes.A7.Landscape());
page.Background(Colors.White);
page.Header().Column(column =>
page.Header().Stack(stack =>
{
column.Item().ShowOnce().Text("This header is visible on the first page.");
column.Item().SkipOnce().Text("This header is visible on the second page and all following.");
stack.Item().ShowOnce().Text("This header is visible on the first page.");
stack.Item().SkipOnce().Text("This header is visible on the second page and all following.");
});
page.Content()
-48
View File
@@ -1,48 +0,0 @@
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class StopPaging
{
[Test]
public void Example()
{
RenderingTest
.Create()
.PageSize(300, 250)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(25)
.DefaultTextStyle(TextStyle.Default.Size(14))
.Decoration(decoration =>
{
decoration
.Before()
.Text(text =>
{
text.DefaultTextStyle(TextStyle.Default.SemiBold().Color(Colors.Blue.Medium));
text.Span("Page ");
text.CurrentPageNumber();
});
decoration
.Content()
.Column(column =>
{
column.Spacing(25);
column.Item().StopPaging().Text(Placeholders.LoremIpsum());
column.Item().ExtendHorizontal().Height(75).Background(Colors.Grey.Lighten2);
});
});
});
}
}
}
-36
View File
@@ -1,36 +0,0 @@
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using SkiaSharp;
using Svg.Skia;
namespace QuestPDF.Examples
{
public class SvgImageExample
{
[Test]
public void BorderRadius()
{
RenderingTest
.Create()
.PageSize(175, 100)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Background(Colors.Grey.Lighten2)
.Padding(25)
.Canvas((canvas, space) =>
{
using var svg = new SKSvg();
svg.Load("pdf-icon.svg");
canvas.DrawPicture(svg.Picture);
});
});
}
}
}
-500
View File
@@ -1,500 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using NUnit.Framework;
using QuestPDF.Drawing;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using IContainer = QuestPDF.Infrastructure.IContainer;
namespace QuestPDF.Examples
{
public class TableExamples
{
[Test]
public void BasicPlacement()
{
RenderingTest
.Create()
.ProduceImages()
.PageSize(220, 220)
.ShowResults()
.Render(container =>
{
container
.Padding(10)
.MinimalBox()
.Border(1)
.Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
});
// by using custom 'Element' method, we can reuse visual configuration
table.Cell().Row(1).Column(4).Element(Block).Text("A");
table.Cell().Row(2).Column(2).Element(Block).Text("B");
table.Cell().Row(3).Column(3).Element(Block).Text("C");
table.Cell().Row(4).Column(1).Element(Block).Text("D");
});
});
}
[Test]
public void PagingSupport()
{
RenderingTest
.Create()
.ProducePdf()
.PageSize(420, 220)
.ShowResults()
.Render(container =>
{
container
.Padding(10)
.MinimalBox()
.Border(1)
.Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
});
// by using custom 'Element' method, we can reuse visual configuration
table.Cell().Element(Block).Text(Placeholders.Label());
table.Cell().Element(Block).Text(Placeholders.Label());
table.Cell().Element(Block).Text(Placeholders.Paragraph());
table.Cell().Element(Block).Text(Placeholders.Label());
});
});
}
[Test]
public void DefaultCellStyle()
{
RenderingTest
.Create()
.ProduceImages()
.PageSize(220, 120)
.ShowResults()
.Render(container =>
{
container
.Padding(10)
.MinimalBox()
.Border(1)
.DefaultTextStyle(TextStyle.Default.Size(16))
.Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
});
table.Cell().Row(1).Column(1).Element(Block).Text("A");
table.Cell().Row(2).Column(2).Element(Block).Text("B");
table.Cell().Row(1).Column(3).Element(Block).Text("C");
table.Cell().Row(2).Column(4).Element(Block).Text("D");
});
});
}
[Test]
public void ColumnsDefinition()
{
RenderingTest
.Create()
.ProduceImages()
.PageSize(320, 80)
.ShowResults()
.Render(container =>
{
container
.Padding(10)
.Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.ConstantColumn(50);
columns.ConstantColumn(100);
columns.RelativeColumn(2);
columns.RelativeColumn(3);
});
table.Cell().ColumnSpan(4).LabelCell("Total width: 300px");
table.Cell().ValueCell("50px");
table.Cell().ValueCell("100px");
table.Cell().ValueCell("100px");
table.Cell().ValueCell("150px");
});
});
}
[Test]
public void PartialAutoPlacement()
{
RenderingTest
.Create()
.ProduceImages()
.PageSize(220, 220)
.ShowResults()
.Render(container =>
{
container
.Padding(10)
.MinimalBox()
.Border(1)
.Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
});
table.Cell().Element(Block).Text("A");
table.Cell().Row(2).Column(2).Element(Block).Text("B");
table.Cell().Element(Block).Text("C");
table.Cell().Row(3).Column(3).Element(Block).Text("D");
table.Cell().ColumnSpan(2).Element(Block).Text("E");
});
});
}
[Test]
public void ExtendLastCellsToTableBottom()
{
RenderingTest
.Create()
.ProduceImages()
.PageSize(220, 170)
.ShowResults()
.Render(container =>
{
container
.Padding(10)
.MinimalBox()
.Border(1)
.Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
});
table.ExtendLastCellsToTableBottom();
table.Cell().Row(1).Column(1).Element(Block).Text("A");
table.Cell().Row(3).Column(1).Element(Block).Text("B");
table.Cell().Row(2).Column(2).Element(Block).Text("C");
table.Cell().Row(3).Column(3).Element(Block).Text("D");
table.Cell().Row(2).RowSpan(2).Column(4).Element(Block).Text("E");
});
});
}
[Test]
public void Overlapping()
{
RenderingTest
.Create()
.ProduceImages()
.PageSize(170, 170)
.ShowResults()
.Render(container =>
{
container
.Padding(10)
.MinimalBox()
.Border(1)
.Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
});
table.Cell().Row(1).RowSpan(3).Column(1).ColumnSpan(3).Background(Colors.Grey.Lighten3).MinHeight(150);
table.Cell().Row(1).RowSpan(2).Column(1).ColumnSpan(2).Background(Colors.Grey.Lighten1).MinHeight(100);
table.Cell().Row(3).Column(3).Background(Colors.Grey.Darken1).MinHeight(50);
});
});
}
[Test]
public void Spans()
{
RenderingTest
.Create()
.ProduceImages()
.PageSize(220, 170)
.ShowResults()
.Render(container =>
{
container
.Padding(10)
.Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
});
table.Cell().RowSpan(2).ColumnSpan(2).Element(Block).Text("1");
table.Cell().ColumnSpan(2).Element(Block).Text("2");
table.Cell().Element(Block).Text("3");
table.Cell().Element(Block).Text("4");
table.Cell().RowSpan(2).Element(Block).Text("5");
table.Cell().ColumnSpan(2).Element(Block).Text("6");
table.Cell().RowSpan(2).Element(Block).Text("7");
table.Cell().Element(Block).Text("8");
table.Cell().Element(Block).Text("9");
});
});
}
[Test]
public void Stability()
{
RenderingTest
.Create()
.ProduceImages()
.PageSize(300, 300)
.ShowResults()
.Render(container =>
{
container
.Padding(10)
.Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
});
table.Cell().RowSpan(4).Element(Block).Text("1");
table.Cell().RowSpan(2).Element(Block).Text("2");
table.Cell().RowSpan(1).Element(Block).Text("3");
table.Cell().RowSpan(1).Element(Block).Text("4");
table.Cell().RowSpan(2).Element(Block).Text("5");
table.Cell().RowSpan(1).Element(Block).Text("6");
table.Cell().RowSpan(1).Element(Block).Text("7");
});
});
}
[Test]
public void TableHeader()
{
RenderingTest
.Create()
.ProduceImages()
.PageSize(500, 200)
.ShowResults()
.EnableDebugging()
.Render(container =>
{
var pageSizes = new List<(string name, double width, double height)>()
{
("Letter (ANSI A)", 8.5f, 11),
("Legal", 8.5f, 14),
("Ledger (ANSI B)", 11, 17),
("Tabloid (ANSI B)", 17, 11),
("ANSI C", 22, 17),
("ANSI D", 34, 22),
("ANSI E", 44, 34)
};
const int inchesToPoints = 72;
container
.Padding(10)
.MinimalBox()
.Border(1)
.Table(table =>
{
IContainer DefaultCellStyle(IContainer container, string backgroundColor)
{
return container
.Border(1)
.BorderColor(Colors.Grey.Lighten1)
.Background(backgroundColor)
.PaddingVertical(5)
.PaddingHorizontal(10)
.AlignCenter()
.AlignMiddle();
}
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn();
columns.ConstantColumn(75);
columns.ConstantColumn(75);
columns.ConstantColumn(75);
columns.ConstantColumn(75);
});
table.Header(header =>
{
header.Cell().RowSpan(2).Element(CellStyle).ExtendHorizontal().AlignLeft().Text("Document type");
header.Cell().ColumnSpan(2).Element(CellStyle).Text("Inches");
header.Cell().ColumnSpan(2).Element(CellStyle).Text("Points");
header.Cell().Element(CellStyle).Text("Width");
header.Cell().Element(CellStyle).Text("Height");
header.Cell().Element(CellStyle).Text("Width");
header.Cell().Element(CellStyle).Text("Height");
// you can extend already existing styles by creating additional methods
IContainer CellStyle(IContainer container) => DefaultCellStyle(container, Colors.Grey.Lighten3);
});
foreach (var page in pageSizes)
{
table.Cell().Element(CellStyle).ExtendHorizontal().AlignLeft().Text(page.name);
// inches
table.Cell().Element(CellStyle).Text(page.width);
table.Cell().Element(CellStyle).Text(page.height);
// points
table.Cell().Element(CellStyle).Text(page.width * inchesToPoints);
table.Cell().Element(CellStyle).Text(page.height * inchesToPoints);
IContainer CellStyle(IContainer container) => DefaultCellStyle(container, Colors.White);
}
});
});
}
[Test]
public void PerformanceText_TemperatureReport()
{
RenderingTest
.Create()
.ProducePdf()
.PageSize(PageSizes.A4)
.MaxPages(10_000)
.EnableCaching()
.EnableDebugging(false)
.ShowResults()
.Render(container => GeneratePerformanceStructure(container, 250));
}
public static void GeneratePerformanceStructure(IContainer container, int repeats)
{
container
.Padding(25)
//.Background(Colors.Blue.Lighten2)
.MinimalBox()
.Border(1)
//.Background(Colors.Red.Lighten2)
.Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.ConstantColumn(100);
columns.RelativeColumn();
columns.ConstantColumn(100);
columns.RelativeColumn();
});
table.ExtendLastCellsToTableBottom();
foreach (var i in Enumerable.Range(0, repeats))
{
table.Cell().RowSpan(3).LabelCell("Project");
table.Cell().RowSpan(3).ShowEntire().ValueCell(Placeholders.Sentence());
table.Cell().LabelCell("Report number");
table.Cell().ValueCell(i.ToString());
table.Cell().LabelCell("Date");
table.Cell().ValueCell(Placeholders.ShortDate());
table.Cell().LabelCell("Inspector");
table.Cell().ValueCell("Marcin Ziąbek");
table.Cell().ColumnSpan(2).LabelCell("Morning weather");
table.Cell().ColumnSpan(2).LabelCell("Evening weather");
table.Cell().ValueCell("Time");
table.Cell().ValueCell("7:13");
table.Cell().ValueCell("Time");
table.Cell().ValueCell("18:25");
table.Cell().ValueCell("Description");
table.Cell().ValueCell("Sunny");
table.Cell().ValueCell("Description");
table.Cell().ValueCell("Windy");
table.Cell().ValueCell("Wind");
table.Cell().ValueCell("Mild");
table.Cell().ValueCell("Wind");
table.Cell().ValueCell("Strong");
table.Cell().ValueCell("Temperature");
table.Cell().ValueCell("17°C");
table.Cell().ValueCell("Temperature");
table.Cell().ValueCell("32°C");
table.Cell().LabelCell("Remarks");
table.Cell().ColumnSpan(3).ValueCell(Placeholders.Paragraph());
}
});
}
// this method uses a higher order function to define a custom and dynamic style
static IContainer Block(IContainer container)
{
return container
.Border(1)
.Background(Colors.Grey.Lighten3)
.ShowOnce()
.MinWidth(50)
.MinHeight(50)
.Padding(10)
.AlignCenter()
.AlignMiddle();
}
}
}
+28 -28
View File
@@ -117,19 +117,19 @@ namespace QuestPDF.Examples
{
decoration
.Content()
.Column(column =>
.Stack(stack =>
{
column.Item().Element(Title);
column.Item().PageBreak();
column.Item().Element(TableOfContents);
column.Item().PageBreak();
stack.Item().Element(Title);
stack.Item().PageBreak();
stack.Item().Element(TableOfContents);
stack.Item().PageBreak();
Chapters(column);
Chapters(stack);
column.Item().Element(Acknowledgements);
stack.Item().Element(Acknowledgements);
});
decoration.After().Element(Footer);
decoration.Footer().Element(Footer);
});
}
@@ -139,61 +139,61 @@ namespace QuestPDF.Examples
.Extend()
.PaddingBottom(200)
.AlignBottom()
.Column(column =>
.Stack(stack =>
{
column.Item().Text("Quo Vadis", TextStyle.Default.Size(72).Bold().Color(Colors.Blue.Darken2));
column.Item().Text("Henryk Sienkiewicz", TextStyle.Default.Size(24).Color(Colors.Grey.Darken2));
stack.Item().Text("Quo Vadis", TextStyle.Default.Size(72).Bold().Color(Colors.Blue.Darken2));
stack.Item().Text("Henryk Sienkiewicz", TextStyle.Default.Size(24).Color(Colors.Grey.Darken2));
});
}
void TableOfContents(IContainer container)
{
container.Column(column =>
container.Stack(stack =>
{
SectionTitle(column, "Spis treści");
SectionTitle(stack, "Spis treści");
foreach (var chapter in chapters)
{
column.Item().InternalLink(chapter.Title).Row(row =>
stack.Item().InternalLink(chapter.Title).Row(row =>
{
row.RelativeItem().Text(chapter.Title, normalStyle);
row.ConstantItem(100).AlignRight().Text(text => text.PageNumberOfLocation(chapter.Title, normalStyle));
row.RelativeColumn().Text(chapter.Title, normalStyle);
row.ConstantColumn(100).AlignRight().Text(text => text.PageNumberOfLocation(chapter.Title, normalStyle));
});
}
});
}
void Chapters(ColumnDescriptor column)
void Chapters(StackDescriptor stack)
{
foreach (var chapter in chapters)
{
column.Item().Element(container => Chapter(container, chapter.Title, chapter.Content));
stack.Item().Element(container => Chapter(container, chapter.Title, chapter.Content));
}
}
void Chapter(IContainer container, string title, string content)
{
container.Column(column =>
container.Stack(stack =>
{
SectionTitle(column, title);
SectionTitle(stack, title);
column.Item().Text(text =>
stack.Item().Text(text =>
{
text.ParagraphSpacing(5);
text.Span(content, normalStyle);
});
column.Item().PageBreak();
stack.Item().PageBreak();
});
}
void Acknowledgements(IContainer container)
{
container.Column(column =>
container.Stack(stack =>
{
SectionTitle(column, "Podziękowania");
SectionTitle(stack, "Podziękowania");
column.Item().Text(text =>
stack.Item().Text(text =>
{
text.DefaultTextStyle(normalStyle);
@@ -204,10 +204,10 @@ namespace QuestPDF.Examples
});
}
void SectionTitle(ColumnDescriptor column, string text)
void SectionTitle(StackDescriptor stack, string text)
{
column.Item().Location(text).Text(text, subtitleStyle);
column.Item().PaddingTop(10).PaddingBottom(50).BorderBottom(1).BorderColor(Colors.Grey.Lighten2).ExtendHorizontal();
stack.Item().Location(text).Text(text, subtitleStyle);
stack.Item().PaddingTop(10).PaddingBottom(50).BorderBottom(1).BorderColor(Colors.Grey.Lighten2).ExtendHorizontal();
}
void Footer(IContainer container)
+10 -10
View File
@@ -24,7 +24,7 @@ namespace QuestPDF.Examples
{
container
.Padding(5)
.MinimalBox()
.Box()
.Border(1)
.Padding(10)
.Text(text =>
@@ -48,7 +48,7 @@ namespace QuestPDF.Examples
{
container
.Padding(5)
.MinimalBox()
.Box()
.Border(1)
.Padding(10)
.Text(text =>
@@ -76,7 +76,7 @@ namespace QuestPDF.Examples
{
container
.Padding(5)
.MinimalBox()
.Box()
.Border(1)
.Padding(10)
.Text(text =>
@@ -108,7 +108,7 @@ namespace QuestPDF.Examples
container
.Padding(20)
.Padding(10)
.MinimalBox()
.Box()
.Border(1)
.Padding(5)
.Padding(10)
@@ -126,7 +126,7 @@ namespace QuestPDF.Examples
}
[Test]
public void Textcolumn()
public void TextStack()
{
RenderingTest
.Create()
@@ -138,7 +138,7 @@ namespace QuestPDF.Examples
container
.Padding(20)
.Padding(10)
.MinimalBox()
.Box()
.Border(1)
.Padding(5)
.Padding(10)
@@ -167,7 +167,7 @@ namespace QuestPDF.Examples
container
.Padding(20)
.Padding(10)
.MinimalBox()
.Box()
.Border(1)
.Padding(5)
.Padding(10)
@@ -230,13 +230,13 @@ namespace QuestPDF.Examples
container
.Padding(20)
.Padding(10)
.MinimalBox()
.Box()
.Border(1)
.Padding(5)
.Padding(10)
.Text(text =>
{
text.DefaultTextStyle(TextStyle.Default.BackgroundColor(Colors.Grey.Lighten3));
text.DefaultTextStyle(TextStyle.Default);
text.AlignLeft();
text.ParagraphSpacing(10);
@@ -251,7 +251,7 @@ namespace QuestPDF.Examples
text.EmptyLine();
foreach (var i in Enumerable.Range(1, 1000))
foreach (var i in Enumerable.Range(1, 100))
{
text.Line($"{i}: {Placeholders.Paragraph()}");
-161
View File
@@ -1,161 +0,0 @@
using System;
using System.Linq;
using System.Text.RegularExpressions;
using NUnit.Framework;
using QuestPDF.Drawing;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace QuestPDF.Examples
{
public class TextShapingTests
{
[Test]
public void ShapeText()
{
using var textPaint = new SKPaint
{
Color = SKColors.Black,
Typeface = SKTypeface.CreateDefault(),
IsAntialias = true,
TextSize = 20
};
using var backgroundPaint = new SKPaint
{
Color = SKColors.LightGray
};
RenderingTest
.Create()
.PageSize(550, 250)
.ProduceImages()
.ShowResults()
.Render(container =>
{
//var lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec odio ipsum, aliquam a neque a, lacinia vehicula lectus.";
//var arabic = "ينا الألم. في بعض الأحيان ونظراً للالتزامات التي يفرضها علينا الواجب والعمل سنتنازل غالباً ونرفض الشعور";
var lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
var arabic = "ينا الألم. في بعض الأحيان ونظراً للالتزامات التي يفرضها علينا";
var text = arabic;
var metrics = textPaint.FontMetrics;
container
.Padding(25)
.Canvas((canvas, space) =>
{
canvas.Translate(0, 20);
var width = MeasureText(text, textPaint);
var widthReal = textPaint.MeasureText(text);
canvas.DrawRect(0, metrics.Descent, width, metrics.Ascent - metrics.Descent, backgroundPaint);
canvas.DrawShapedText(text, 0, 0, textPaint);
canvas.Translate(0, 40);
canvas.DrawText(text, 0, 0, textPaint);
});
});
}
float MeasureText(string text, SKPaint paint)
{
var font = paint.ToFont();
using var shaper = new SKShaper(paint.Typeface);
var result = shaper.Shape(text + " ", paint);
return Math.Max(result.Points.First().X, result.Points.Last().X);
var glyphCount = result.Codepoints.Length;
var glyphIds = new ushort[glyphCount];
var glyphWidths = new float[glyphCount];
var glyphBounds = new SKRect[glyphCount];
font.GetGlyphs(result.Codepoints.Select(x => (int)x).ToArray(), glyphIds);
font.GetGlyphWidths(glyphIds, glyphWidths, glyphBounds);
return glyphWidths.Sum();
return result.Points.Max(p => p.X);
using var skTextBlobBuilder = new SKTextBlobBuilder();
var positionedRunBuffer = skTextBlobBuilder.AllocatePositionedRun(font, result.Codepoints.Length);
var glyphSpan = positionedRunBuffer.GetGlyphSpan();
var positionSpan = positionedRunBuffer.GetPositionSpan();
for (int index = 0; index < result.Codepoints.Length; ++index)
{
glyphSpan[index] = (ushort) result.Codepoints[index];
positionSpan[index] = result.Points[index];
}
using var text1 = skTextBlobBuilder.Build();
return text1.Bounds.Width;
}
[Test]
public void ShapeSingle()
{
using var textPaint = new SKPaint
{
Color = SKColors.Black,
Typeface = SKTypeface.CreateDefault(),
IsAntialias = true,
TextSize = 20
};
var text = string.Join(" ", Enumerable.Range(0, 10_000).Select(x => Placeholders.Sentence()));
using var shaper = new SKShaper(textPaint.Typeface);
var result = shaper.Shape(text + " ", textPaint);
}
[Test]
public void ShapeMany()
{
using var textPaint = new SKPaint
{
Color = SKColors.Black,
Typeface = SKTypeface.CreateDefault(),
IsAntialias = true,
TextSize = 20
};
var text = string.Join(" ", Enumerable.Range(0, 1_000).Select(x => Placeholders.Sentence()));
foreach (var part in Regex.Split(text, "( )|(\\w+)"))
{
using var shaper = new SKShaper(textPaint.Typeface);
var result = shaper.Shape(part + " ", textPaint);
}
}
[Test]
public void ShapeMany2()
{
using var textPaint = new SKPaint
{
Color = SKColors.Black,
Typeface = SKTypeface.CreateDefault(),
IsAntialias = true,
TextSize = 20
};
using var shaper = new SKShaper(textPaint.Typeface);
foreach (var i in Enumerable.Range(0, 10_000))
{
var text = Placeholders.Paragraph();
textPaint.MeasureText(text);
var result = shaper.Shape(text + " ", textPaint);
}
}
}
}
-149
View File
@@ -1,149 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="75.320129mm"
height="92.604164mm"
viewBox="0 0 75.320129 92.604164"
version="1.1"
id="svg8"
inkscape:version="0.92.3 (2405546, 2018-03-11)"
sodipodi:docname="PDF file icon.svg">
<title
id="title2682">PDF file icon</title>
<defs
id="defs2">
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath839">
<rect
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
id="rect841"
width="68.688263"
height="67.886459"
x="-74.033661"
y="141.44913" />
</clipPath>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.98994949"
inkscape:cx="122.12727"
inkscape:cy="167.02823"
inkscape:document-units="mm"
inkscape:current-layer="g899"
showgrid="false"
inkscape:object-nodes="false"
inkscape:window-width="1366"
inkscape:window-height="705"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:snap-page="false"
inkscape:snap-others="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>PDF file icon</dc:title>
<dc:date>08/10/2018</dc:date>
<dc:creator>
<cc:Agent>
<dc:title>Adobe Systems</dc:title>
</cc:Agent>
</dc:creator>
<dc:publisher>
<cc:Agent>
<dc:title>CMetalCore</dc:title>
</cc:Agent>
</dc:publisher>
<dc:description>Fuente del texto &quot;PDF&quot;:
Franklin Gothic Medium Cond</dc:description>
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
</cc:License>
</rdf:RDF>
</metadata>
<g
inkscape:label="Capa 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(36.076172,-93.731774)">
<g
id="g899"
transform="matrix(1.4843054,0,0,1.4843054,17.471885,-90.243502)">
<g
id="g876">
<path
inkscape:connector-curvature="0"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ff2116;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.11666656;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m -29.632812,123.94727 c -3.551967,0 -6.44336,2.89347 -6.44336,6.44531 v 49.49804 c 0,3.55185 2.891393,6.44532 6.44336,6.44532 H 8.2167969 c 3.5519661,0 6.4433591,-2.89335 6.4433591,-6.44532 v -40.70117 c 0,0 0.101353,-1.19181 -0.416015,-2.35156 -0.484969,-1.08711 -1.275391,-1.84375 -1.275391,-1.84375 a 1.0584391,1.0584391 0 0 0 -0.0059,-0.008 L 3.5722246,125.7752 a 1.0584391,1.0584391 0 0 0 -0.015625,-0.0156 c 0,0 -0.8017392,-0.76344 -1.9902344,-1.27344 -1.39939552,-0.6005 -2.8417968,-0.53711 -2.8417968,-0.53711 l 0.021484,-0.002 z"
id="path890" />
<path
inkscape:connector-curvature="0"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#f5f5f5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.11666656;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m -29.632812,126.06445 h 28.3789058 a 1.0584391,1.0584391 0 0 0 0.021484,0 c 0,0 1.13480448,0.011 1.96484378,0.36719 0.79889772,0.34282 1.36536982,0.86176 1.36914062,0.86524 1.25e-5,1e-5 0.00391,0.004 0.00391,0.004 l 9.3671868,9.18945 c 0,0 0.564354,0.59582 0.837891,1.20899 0.220779,0.49491 0.234375,1.40039 0.234375,1.40039 a 1.0584391,1.0584391 0 0 0 -0.002,0.0449 v 40.74609 c 0,2.41592 -1.910258,4.32813 -4.3261717,4.32813 H -29.632812 c -2.415914,0 -4.326172,-1.91209 -4.326172,-4.32813 v -49.49804 c 0,-2.41603 1.910258,-4.32813 4.326172,-4.32813 z"
id="rect2684" />
<path
sodipodi:nodetypes="scccscccscccccssscacccsccccccccccccccccccccaccccccccc"
inkscape:connector-curvature="0"
d="m -23.40766,161.09299 c -1.45669,-1.45669 0.11934,-3.45839 4.39648,-5.58397 l 2.69124,-1.33743 1.04845,-2.29399 c 0.57665,-1.26169 1.43729,-3.32036 1.91254,-4.5748 l 0.8641,-2.28082 -0.59546,-1.68793 c -0.73217,-2.07547 -0.99326,-5.19438 -0.52872,-6.31588 0.62923,-1.51909 2.69029,-1.36323 3.50626,0.26515 0.63727,1.27176 0.57212,3.57488 -0.18329,6.47946 l -0.6193,2.38125 0.5455,0.92604 c 0.30003,0.50932 1.1764,1.71867 1.9475,2.68743 l 1.44924,1.80272 1.8033728,-0.23533 c 5.72900399,-0.74758 7.6912472,0.523 7.6912472,2.34476 0,2.29921 -4.4984914,2.48899 -8.2760865,-0.16423 -0.8499666,-0.59698 -1.4336605,-1.19001 -1.4336605,-1.19001 0,0 -2.3665326,0.48178 -3.531704,0.79583 -1.202707,0.32417 -1.80274,0.52719 -3.564509,1.12186 0,0 -0.61814,0.89767 -1.02094,1.55026 -1.49858,2.4279 -3.24833,4.43998 -4.49793,5.1723 -1.3991,0.81993 -2.86584,0.87582 -3.60433,0.13733 z m 2.28605,-0.81668 c 0.81883,-0.50607 2.47616,-2.46625 3.62341,-4.28553 l 0.46449,-0.73658 -2.11497,1.06339 c -3.26655,1.64239 -4.76093,3.19033 -3.98386,4.12664 0.43653,0.52598 0.95874,0.48237 2.01093,-0.16792 z m 21.21809,-5.95578 c 0.80089,-0.56097 0.68463,-1.69142 -0.22082,-2.1472 -0.70466,-0.35471 -1.2726074,-0.42759 -3.1031574,-0.40057 -1.1249,0.0767 -2.9337647,0.3034 -3.2403347,0.37237 0,0 0.993716,0.68678 1.434896,0.93922 0.58731,0.33544 2.0145161,0.95811 3.0565161,1.27706 1.02785,0.31461 1.6224,0.28144 2.0729,-0.0409 z m -8.53152,-3.54594 c -0.4847,-0.50952 -1.30889,-1.57296 -1.83152,-2.3632 -0.68353,-0.89643 -1.02629,-1.52887 -1.02629,-1.52887 0,0 -0.4996,1.60694 -0.90948,2.57394 l -1.27876,3.16076 -0.37075,0.71695 c 0,0 1.971043,-0.64627 2.97389,-0.90822 1.0621668,-0.27744 3.21787,-0.70134 3.21787,-0.70134 z m -2.74938,-11.02573 c 0.12363,-1.0375 0.1761,-2.07346 -0.15724,-2.59587 -0.9246,-1.01077 -2.04057,-0.16787 -1.85154,2.23517 0.0636,0.8084 0.26443,2.19033 0.53292,3.04209 l 0.48817,1.54863 0.34358,-1.16638 c 0.18897,-0.64151 0.47882,-2.02015 0.64411,-3.06364 z"
style="fill:#ff2116;fill-opacity:1;stroke-width:0.26458335"
id="path2697" />
<g
id="g858">
<path
inkscape:connector-curvature="0"
id="path845"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:125%;font-family:'Franklin Gothic Medium Cond';-inkscape-font-specification:'Franklin Gothic Medium Cond';letter-spacing:0px;word-spacing:4.26000023px;fill:#2c2c2c;fill-opacity:1;stroke:none;stroke-width:0.35824656px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m -20.930423,167.83862 h 2.364986 q 1.133514,0 1.840213,0.2169 0.706698,0.20991 1.189489,0.9446 0.482795,0.72769 0.482795,1.75625 0,0.94459 -0.391832,1.6233 -0.391833,0.67871 -1.056548,0.97958 -0.65772,0.30087 -2.02913,0.30087 h -0.818651 v 3.72941 h -1.581322 z m 1.581322,1.22447 v 3.33058 h 0.783664 q 1.049552,0 1.44838,-0.39184 0.405826,-0.39183 0.405826,-1.27345 0,-0.65772 -0.265887,-1.06355 -0.265884,-0.41282 -0.587747,-0.50378 -0.314866,-0.098 -1.000572,-0.098 z" />
<path
inkscape:connector-curvature="0"
id="path847"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:125%;font-family:'Franklin Gothic Medium Cond';-inkscape-font-specification:'Franklin Gothic Medium Cond';letter-spacing:0px;word-spacing:4.26000023px;fill:#2c2c2c;fill-opacity:1;stroke:none;stroke-width:0.35824656px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m -13.842461,167.83862 h 2.148082 q 1.560333,0 2.4909318,0.55276 0.9375993,0.55276 1.4133973,1.6443 0.482791,1.09153 0.482791,2.42096 0,1.3994 -0.4338151,2.49793 -0.4268149,1.09153 -1.3154348,1.76324 -0.8816233,0.67172 -2.5189212,0.67172 h -2.267031 z m 1.581326,1.26645 v 7.018 h 0.657715 q 1.378411,0 2.001144,-0.9516 0.6227329,-0.95858 0.6227329,-2.5539 0,-3.5125 -2.6238769,-3.5125 z" />
<path
inkscape:connector-curvature="0"
id="path849"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:125%;font-family:'Franklin Gothic Medium Cond';-inkscape-font-specification:'Franklin Gothic Medium Cond';letter-spacing:0px;word-spacing:4.26000023px;fill:#2c2c2c;fill-opacity:1;stroke:none;stroke-width:0.35824656px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m -5.7889096,167.83862 h 5.30372941 v 1.26645 H -4.2075842 v 2.85478 h 2.9807225 v 1.26646 h -2.9807225 v 4.16322 h -1.5813254 z" />
</g>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 12 KiB

-28
View File
@@ -1,5 +1,4 @@
using System;
using System.Collections.Concurrent;
using System.IO;
using SkiaSharp;
@@ -25,32 +24,5 @@ namespace QuestPDF.ReportSample
Latitude = Helpers.Random.NextDouble() * 180f - 90f
};
}
private static readonly ConcurrentDictionary<int, string> RomanNumeralCache = new ConcurrentDictionary<int, string>();
public static string FormatAsRomanNumeral(this int number)
{
if (number < 0 || number > 3999)
throw new ArgumentOutOfRangeException("Number should be in range from 1 to 3999");
return RomanNumeralCache.GetOrAdd(number, x =>
{
if (x >= 1000) return "M" + FormatAsRomanNumeral(x - 1000);
if (x >= 900) return "CM" + FormatAsRomanNumeral(x - 900);
if (x >= 500) return "D" + FormatAsRomanNumeral(x - 500);
if (x >= 400) return "CD" + FormatAsRomanNumeral(x - 400);
if (x >= 100) return "C" + FormatAsRomanNumeral(x - 100);
if (x >= 90) return "XC" + FormatAsRomanNumeral(x - 90);
if (x >= 50) return "L" + FormatAsRomanNumeral(x - 50);
if (x >= 40) return "XL" + FormatAsRomanNumeral(x - 40);
if (x >= 10) return "X" + FormatAsRomanNumeral(x - 10);
if (x >= 9) return "IX" + FormatAsRomanNumeral(x - 9);
if (x >= 5) return "V" + FormatAsRomanNumeral(x - 5);
if (x >= 4) return "IV" + FormatAsRomanNumeral(x - 4);
if (x >= 1) return "I" + FormatAsRomanNumeral(x - 1);
return string.Empty;
});
}
}
}
@@ -26,58 +26,58 @@ namespace QuestPDF.ReportSample.Layouts
private void ComposeHeader(IContainer container)
{
container.Background(Colors.Grey.Lighten3).Border(1).Column(column =>
container.Background(Colors.Grey.Lighten3).Border(1).Stack(stack =>
{
column.Item().ShowOnce().Padding(5).AlignMiddle().Row(row =>
stack.Item().ShowOnce().Padding(5).AlignMiddle().Row(row =>
{
row.RelativeItem(2).AlignMiddle().Text("PRIMARY HEADER", TextStyle.Default.Color(Colors.Grey.Darken3).Size(30).Bold());
row.RelativeItem(1).AlignRight().MinimalBox().AlignMiddle().Background(Colors.Blue.Darken2).Padding(30);
row.RelativeColumn(2).AlignMiddle().Text("PRIMARY HEADER", TextStyle.Default.Color(Colors.Grey.Darken3).Size(30).Bold());
row.RelativeColumn(1).AlignRight().Box().AlignMiddle().Background(Colors.Blue.Darken2).Padding(30);
});
column.Item().SkipOnce().Padding(5).Row(row =>
stack.Item().SkipOnce().Padding(5).Row(row =>
{
row.RelativeItem(2).Text("SECONDARY HEADER", TextStyle.Default.Color(Colors.Grey.Darken3).Size(30).Bold());
row.RelativeItem(1).AlignRight().MinimalBox().Background(Colors.Blue.Lighten4).Padding(15);
row.RelativeColumn(2).Text("SECONDARY HEADER", TextStyle.Default.Color(Colors.Grey.Darken3).Size(30).Bold());
row.RelativeColumn(1).AlignRight().Box().Background(Colors.Blue.Lighten4).Padding(15);
});
});
}
private void ComposeContent(IContainer container)
{
container.Column(column =>
container.Stack(stack =>
{
column.Item().PaddingVertical(80).Text("First");
column.Item().PageBreak();
column.Item().PaddingVertical(80).Text("Second");
column.Item().PageBreak();
column.Item().PaddingVertical(80).Text("Third");
column.Item().PageBreak();
stack.Item().PaddingVertical(80).Text("First");
stack.Item().PageBreak();
stack.Item().PaddingVertical(80).Text("Second");
stack.Item().PageBreak();
stack.Item().PaddingVertical(80).Text("Third");
stack.Item().PageBreak();
});
}
private void ComposeFooter(IContainer container)
{
container.Background(Colors.Grey.Lighten3).Column(column =>
container.Background(Colors.Grey.Lighten3).Stack(stack =>
{
column.Item().ShowOnce().Background(Colors.Grey.Lighten3).Row(row =>
stack.Item().ShowOnce().Background(Colors.Grey.Lighten3).Row(row =>
{
row.RelativeItem().Text(x =>
row.RelativeColumn().Text(x =>
{
x.CurrentPageNumber();
x.Span(" / ");
x.TotalPages();
});
row.RelativeItem().AlignRight().Text("Footer for header");
row.RelativeColumn().AlignRight().Text("Footer for header");
});
column.Item().SkipOnce().Background(Colors.Grey.Lighten3).Row(row =>
stack.Item().SkipOnce().Background(Colors.Grey.Lighten3).Row(row =>
{
row.RelativeItem().Text(x =>
row.RelativeColumn().Text(x =>
{
x.CurrentPageNumber();
x.Span(" / ");
x.TotalPages();
});
row.RelativeItem().AlignRight().Text("Footer for every page except header");
row.RelativeColumn().AlignRight().Text("Footer for every page except header");
});
});
}
@@ -17,11 +17,11 @@ namespace QuestPDF.ReportSample.Layouts
{
container
.ShowEntire()
.Column(column =>
.Stack(stack =>
{
column.Spacing(5);
column.Item().Element(PhotoWithMaps);
column.Item().Element(PhotoDetails);
stack.Spacing(5);
stack.Item().Element(PhotoWithMaps);
stack.Item().Element(PhotoDetails);
});
}
@@ -30,14 +30,14 @@ namespace QuestPDF.ReportSample.Layouts
container
.Row(row =>
{
row.RelativeItem(2).AspectRatio(4 / 3f).Component<ImagePlaceholder>();
row.RelativeColumn(2).AspectRatio(4 / 3f).Component<ImagePlaceholder>();
row.RelativeItem().PaddingLeft(5).Column(column =>
row.RelativeColumn().PaddingLeft(5).Stack(stack =>
{
column.Spacing(7f);
stack.Spacing(7f);
column.Item().AspectRatio(4 / 3f).Component<ImagePlaceholder>();
column.Item().AspectRatio(4 / 3f).Component<ImagePlaceholder>();
stack.Item().AspectRatio(4 / 3f).Component<ImagePlaceholder>();
stack.Item().AspectRatio(4 / 3f).Component<ImagePlaceholder>();
});
});
}
@@ -21,25 +21,25 @@ namespace QuestPDF.ReportSample.Layouts
.Decoration(decoration =>
{
decoration
.Before()
.Header()
.PaddingBottom(5)
.Text(Model.Title, Typography.Headline);
decoration.Content().Border(0.75f).BorderColor(Colors.Grey.Medium).Column(column =>
decoration.Content().Border(0.75f).BorderColor(Colors.Grey.Medium).Stack(stack =>
{
foreach (var part in Model.Parts)
{
column.Item().EnsureSpace(25).Row(row =>
stack.Item().EnsureSpace(25).Row(row =>
{
row.ConstantItem(150).LabelCell().Text(part.Label);
var frame = row.RelativeItem().ValueCell();
row.ConstantColumn(150).LabelCell().Text(part.Label);
var frame = row.RelativeColumn().ValueCell();
if (part is ReportSectionText text)
frame.ShowEntire().Text(text.Text);
if (part is ReportSectionMap map)
frame.Element(x => MapElement(x, map));
if (part is ReportSectionPhotos photos)
frame.Element(x => PhotosElement(x, photos));
});
@@ -56,12 +56,12 @@ namespace QuestPDF.ReportSample.Layouts
return;
}
container.ShowEntire().Column(column =>
container.ShowEntire().Stack(stack =>
{
column.Spacing(5);
stack.Spacing(5);
column.Item().MaxWidth(250).AspectRatio(4 / 3f).Component<ImagePlaceholder>();
column.Item().Text(model.Location.Format());
stack.Item().MaxWidth(250).AspectRatio(4 / 3f).Component<ImagePlaceholder>();
stack.Item().Text(model.Location.Format());
});
}
+18 -18
View File
@@ -39,28 +39,28 @@ namespace QuestPDF.ReportSample.Layouts
page.Footer().AlignCenter().Text(text =>
{
text.CurrentPageNumber(format: x => x?.FormatAsRomanNumeral() ?? "-----");
text.CurrentPageNumber();
text.Span(" / ");
text.TotalPages(format: x => x?.FormatAsRomanNumeral() ?? "-----");
text.TotalPages();
});
});
}
private void ComposeHeader(IContainer container)
{
container.Column(column =>
container.Stack(stack =>
{
column.Item().Row(row =>
stack.Item().Row(row =>
{
row.Spacing(50);
row.RelativeItem().PaddingTop(-10).Text(Model.Title, Typography.Title);
row.ConstantItem(90).ExternalLink("https://www.questpdf.com").MaxHeight(30).Component<ImagePlaceholder>();
row.RelativeColumn().PaddingTop(-10).Text(Model.Title, Typography.Title);
row.ConstantColumn(90).ExternalLink("https://www.questpdf.com").MaxHeight(30).Component<ImagePlaceholder>();
});
column.Item().ShowOnce().PaddingVertical(15).Border(1f).BorderColor(Colors.Grey.Lighten1).ExtendHorizontal();
stack.Item().ShowOnce().PaddingVertical(15).Border(1f).BorderColor(Colors.Grey.Lighten1).ExtendHorizontal();
column.Item().ShowOnce().Grid(grid =>
stack.Item().ShowOnce().Grid(grid =>
{
grid.Columns(2);
grid.Spacing(5);
@@ -79,22 +79,22 @@ namespace QuestPDF.ReportSample.Layouts
void ComposeContent(IContainer container)
{
container.PaddingVertical(20).Column(column =>
container.PaddingVertical(20).Stack(stack =>
{
column.Spacing(20);
stack.Spacing(20);
stack.Item().Component(new TableOfContentsTemplate(Model.Sections));
column.Item().Component(new TableOfContentsTemplate(Model.Sections));
column.Item().PageBreak();
stack.Item().PageBreak();
foreach (var section in Model.Sections)
column.Item().Location(section.Title).Component(new SectionTemplate(section));
column.Item().PageBreak();
column.Item().Location("Photos");
stack.Item().Location(section.Title).Component(new SectionTemplate(section));
stack.Item().PageBreak();
stack.Item().Location("Photos");
foreach (var photo in Model.Photos)
column.Item().Component(new PhotoTemplate(photo));
stack.Item().Component(new PhotoTemplate(photo));
});
}
}
@@ -1,7 +1,5 @@
using System.Collections.Generic;
using System.Drawing;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.ReportSample.Layouts
@@ -21,18 +19,18 @@ namespace QuestPDF.ReportSample.Layouts
.Decoration(decoration =>
{
decoration
.Before()
.Header()
.PaddingBottom(5)
.Text("Table of contents", Typography.Headline);
decoration.Content().Column(column =>
decoration.Content().Stack(stack =>
{
column.Spacing(5);
stack.Spacing(5);
for (var i = 0; i < Sections.Count; i++)
column.Item().Element(c => DrawLink(c, i+1, Sections[i].Title));
stack.Item().Element(c => DrawLink(c, i+1, Sections[i].Title));
column.Item().Element(c => DrawLink(c, Sections.Count+1, "Photos"));
stack.Item().Element(c => DrawLink(c, Sections.Count+1, "Photos"));
});
});
}
@@ -43,20 +41,9 @@ namespace QuestPDF.ReportSample.Layouts
.InternalLink(locationName)
.Row(row =>
{
row.ConstantItem(25).Text($"{number}.");
row.RelativeItem().Text(locationName);
row.ConstantItem(150).AlignRight().Text(text =>
{
text.BeginPageNumberOfSection(locationName);
text.Span(" - ");
text.EndPageNumberOfSection(locationName);
var lengthStyle = TextStyle.Default.Color(Colors.Grey.Medium);
text.Span(" (", lengthStyle);
text.TotalPagesWithinSection(locationName, lengthStyle, x => x == 1 ? "1 page long" : $"{x} pages long");
text.Span(")", lengthStyle);
});
row.ConstantColumn(25).Text($"{number}.");
row.RelativeColumn().Text(locationName);
row.ConstantColumn(150).AlignRight().Text(text => text.PageNumberOfLocation(locationName));
});
}
}
+1 -2
View File
@@ -11,7 +11,6 @@ using NUnit.Framework;
using QuestPDF.Drawing;
using QuestPDF.Drawing.Proxy;
using QuestPDF.Elements;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using QuestPDF.ReportSample.Layouts;
@@ -56,7 +55,7 @@ namespace QuestPDF.ReportSample
var sw = new Stopwatch();
sw.Start();
Content.VisitChildren(x =>
Content.HandleVisitor(x =>
{
if (x is ICacheable)
x.CreateProxy(y => new CacheProxy(y));
-2
View File
@@ -24,8 +24,6 @@ namespace QuestPDF.ReportSample
[Test]
public void GenerateAndShowPdf()
{
//ImagePlaceholder.Solid = true;
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"test_result.pdf");
Report.GeneratePdf(path);
Process.Start("explorer.exe", path);
+2 -2
View File
@@ -21,7 +21,7 @@ namespace QuestPDF.UnitTests
Color = Colors.Red.Medium
})
.DrawElement(new Size(400, 300))
.ExpectCanvasDrawRectangle(new Position(0, 0), new Size(400, 300), Colors.Red.Medium)
.ExpectCanvasDrawFilledRectangle(new Position(0, 0), new Size(400, 300), Colors.Red.Medium)
.CheckDrawResult();
}
@@ -35,7 +35,7 @@ namespace QuestPDF.UnitTests
Child = x.CreateChild()
})
.DrawElement(new Size(400, 300))
.ExpectCanvasDrawRectangle(new Position(0, 0), new Size(400, 300), Colors.Red.Medium)
.ExpectCanvasDrawFilledRectangle(new Position(0, 0), new Size(400, 300), Colors.Red.Medium)
.ExpectChildDraw(new Size(400, 300))
.CheckDrawResult();
}
+30 -6
View File
@@ -1,9 +1,11 @@
using NUnit.Framework;
using System.Drawing;
using NUnit.Framework;
using QuestPDF.Drawing;
using QuestPDF.Elements;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using QuestPDF.UnitTests.TestEngine;
using Size = QuestPDF.Infrastructure.Size;
namespace QuestPDF.UnitTests
{
@@ -32,7 +34,29 @@ namespace QuestPDF.UnitTests
}
[Test]
public void Draw_HorizontalRight_VerticalTop()
public void Draw_SameWidths_Optimized()
{
TestPlan
.For(x => new Border
{
Top = 10,
Right = 10,
Bottom = 10,
Left = 10,
Color = Colors.Red.Medium,
Child = x.CreateChild()
})
.DrawElement(new Size(400, 300))
.ExpectChildDraw(new Size(400, 300))
.ExpectCanvasDrawStrokedRectangle(new Size(400, 300), Colors.Red.Medium, 10)
.ExpectChildDraw(new Size(400, 300))
.CheckDrawResult();
}
[Test]
public void Draw_VariousWidths()
{
TestPlan
.For(x => new Border
@@ -48,10 +72,10 @@ namespace QuestPDF.UnitTests
})
.DrawElement(new Size(400, 300))
.ExpectChildDraw(new Size(400, 300))
.ExpectCanvasDrawRectangle(new Position(-20, -5), new Size(430, 10), Colors.Red.Medium) // top
.ExpectCanvasDrawRectangle(new Position(-20, -5), new Size(40, 320), Colors.Red.Medium) // left
.ExpectCanvasDrawRectangle(new Position(-20, 285), new Size(430, 30), Colors.Red.Medium) // bottom
.ExpectCanvasDrawRectangle(new Position(390, -5), new Size(20, 320), Colors.Red.Medium) // right
.ExpectCanvasDrawFilledRectangle(new Position(-20, -5), new Size(430, 10), Colors.Red.Medium) // top
.ExpectCanvasDrawFilledRectangle(new Position(-20, -5), new Size(40, 320), Colors.Red.Medium) // left
.ExpectCanvasDrawFilledRectangle(new Position(-20, 285), new Size(430, 30), Colors.Red.Medium) // bottom
.ExpectCanvasDrawFilledRectangle(new Position(390, -5), new Size(20, 320), Colors.Red.Medium) // right
.ExpectChildDraw(new Size(400, 300))
.CheckDrawResult();
}
+4 -4
View File
@@ -10,13 +10,13 @@ namespace QuestPDF.UnitTests
public class BoxTests
{
[Test]
public void Measure() => SimpleContainerTests.Measure<MinimalBox>();
public void Measure() => SimpleContainerTests.Measure<Box>();
[Test]
public void Draw_Wrap()
{
TestPlan
.For(x => new MinimalBox
.For(x => new Box
{
Child = x.CreateChild()
})
@@ -29,7 +29,7 @@ namespace QuestPDF.UnitTests
public void Measure_PartialRender()
{
TestPlan
.For(x => new MinimalBox
.For(x => new Box
{
Child = x.CreateChild()
})
@@ -43,7 +43,7 @@ namespace QuestPDF.UnitTests
public void Measure_FullRender()
{
TestPlan
.For(x => new MinimalBox
.For(x => new Box
{
Child = x.CreateChild()
})
+80 -83
View File
@@ -9,27 +9,35 @@ namespace QuestPDF.UnitTests
[TestFixture]
public class DecorationTests
{
private Decoration CreateDecoration(TestPlan testPlan)
{
return new Decoration
{
Before = testPlan.CreateChild("before"),
Content = testPlan.CreateChild("content"),
After = testPlan.CreateChild("after"),
};
}
#region Measure
[Test]
public void Measure_ReturnsWrap_WhenBeforeReturnsWrap()
public void Measure_ReturnsWrap_WhenDecorationReturnsWrap()
{
TestPlan
.For(CreateDecoration)
.For(x => new BinaryDecoration
{
Type = DecorationType.Append,
DecorationElement = x.CreateChild("decoration"),
ContentElement = x.CreateChild("content")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("before", new Size(400, 300), SpacePlan.Wrap())
.ExpectChildMeasure("after", new Size(400, 300), SpacePlan.FullRender(100, 50))
.ExpectChildMeasure("content", new Size(400, 250), SpacePlan.FullRender(100, 100))
.ExpectChildMeasure("decoration", new Size(400, 300), SpacePlan.Wrap())
.CheckMeasureResult(SpacePlan.Wrap());
}
[Test]
public void Measure_ReturnsWrap_WhenDecorationReturnsPartialRender()
{
TestPlan
.For(x => new BinaryDecoration
{
Type = DecorationType.Append,
DecorationElement = x.CreateChild("decoration"),
ContentElement = x.CreateChild("content")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("decoration", new Size(400, 300), SpacePlan.PartialRender(300, 200))
.CheckMeasureResult(SpacePlan.Wrap());
}
@@ -37,100 +45,89 @@ namespace QuestPDF.UnitTests
public void Measure_ReturnsWrap_WhenContentReturnsWrap()
{
TestPlan
.For(CreateDecoration)
.For(x => new BinaryDecoration
{
Type = DecorationType.Append,
DecorationElement = x.CreateChild("decoration"),
ContentElement = x.CreateChild("content")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("before", new Size(400, 300), SpacePlan.FullRender(100, 50))
.ExpectChildMeasure("after", new Size(400, 300), SpacePlan.FullRender(100, 50))
.ExpectChildMeasure("decoration", new Size(400, 300), SpacePlan.FullRender(300, 100))
.ExpectChildMeasure("content", new Size(400, 200), SpacePlan.Wrap())
.CheckMeasureResult(SpacePlan.Wrap());
}
[Test]
public void Measure_ReturnsWrap_WhenAfterReturnsWrap()
public void Measure_ReturnsPartialRender_WhenContentReturnsPartialRender()
{
TestPlan
.For(CreateDecoration)
.For(x => new BinaryDecoration
{
Type = DecorationType.Append,
DecorationElement = x.CreateChild("decoration"),
ContentElement = x.CreateChild("content")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("before", new Size(400, 300), SpacePlan.FullRender(100, 50))
.ExpectChildMeasure("after", new Size(400, 300), SpacePlan.Wrap())
.ExpectChildMeasure("content", new Size(400, 250), SpacePlan.FullRender(100, 100))
.CheckMeasureResult(SpacePlan.Wrap());
.ExpectChildMeasure("decoration", new Size(400, 300), SpacePlan.FullRender(300, 100))
.ExpectChildMeasure("content", new Size(400, 200), SpacePlan.PartialRender(200, 150))
.CheckMeasureResult(SpacePlan.PartialRender(400, 250));
}
[Test]
public void Measure_ReturnsWrap_WhenBeforeReturnsPartialRender()
public void Measure_ReturnsFullRender_WhenContentReturnsFullRender()
{
TestPlan
.For(CreateDecoration)
.For(x => new BinaryDecoration
{
Type = DecorationType.Append,
DecorationElement = x.CreateChild("decoration"),
ContentElement = x.CreateChild("content")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("before", new Size(400, 300), SpacePlan.PartialRender(100, 50))
.ExpectChildMeasure("after", new Size(400, 300), SpacePlan.FullRender(100, 50))
.ExpectChildMeasure("content", new Size(400, 250), SpacePlan.FullRender(100, 100))
.CheckMeasureResult(SpacePlan.Wrap());
.ExpectChildMeasure("decoration", new Size(400, 300), SpacePlan.FullRender(300, 100))
.ExpectChildMeasure("content", new Size(400, 200), SpacePlan.FullRender(200, 150))
.CheckMeasureResult(SpacePlan.FullRender(400, 250));
}
[Test]
public void Measure_ReturnsWrap_WhenAfterReturnsPartialRender()
{
TestPlan
.For(CreateDecoration)
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("before", new Size(400, 300), SpacePlan.FullRender(100, 50))
.ExpectChildMeasure("after", new Size(400, 300), SpacePlan.PartialRender(100, 50))
.ExpectChildMeasure("content", new Size(400, 250), SpacePlan.FullRender(100, 100))
.CheckMeasureResult(SpacePlan.Wrap());
}
[Test]
public void Measure_ReturnsWrap_WhenContentReturnsPartialRender()
{
TestPlan
.For(CreateDecoration)
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("before", new Size(400, 300), SpacePlan.FullRender(100, 50))
.ExpectChildMeasure("after", new Size(400, 300), SpacePlan.FullRender(100, 50))
.ExpectChildMeasure("content", new Size(400, 200), SpacePlan.PartialRender(150, 100))
.CheckMeasureResult(SpacePlan.PartialRender(150, 200));
}
[Test]
public void Measure_ReturnsWrap_WhenContentReturnsFullRender()
{
TestPlan
.For(CreateDecoration)
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("before", new Size(400, 300), SpacePlan.FullRender(100, 50))
.ExpectChildMeasure("after", new Size(400, 300), SpacePlan.FullRender(100, 50))
.ExpectChildMeasure("content", new Size(400, 200), SpacePlan.FullRender(150, 100))
.CheckMeasureResult(SpacePlan.FullRender(150, 200));
}
#endregion
#region Draw
[Test]
public void Draw_Prepend()
{
TestPlan
.For(x => new BinaryDecoration
{
Type = DecorationType.Prepend,
DecorationElement = x.CreateChild("decoration"),
ContentElement = x.CreateChild("content")
})
.DrawElement(new Size(400, 300))
.ExpectChildMeasure("decoration", new Size(400, 300), SpacePlan.FullRender(300, 100))
.ExpectChildDraw("decoration", new Size(400, 100))
.ExpectCanvasTranslate(0, 100)
.ExpectChildDraw("content", new Size(400, 200))
.ExpectCanvasTranslate(0, -100)
.CheckDrawResult();
}
[Test]
public void Draw_Append()
{
TestPlan
.For(CreateDecoration)
.For(x => new BinaryDecoration
{
Type = DecorationType.Append,
DecorationElement = x.CreateChild("decoration"),
ContentElement = x.CreateChild("content")
})
.DrawElement(new Size(400, 300))
.ExpectChildMeasure("before", new Size(400, 300), SpacePlan.FullRender(200, 40))
.ExpectChildMeasure("after", new Size(400, 300), SpacePlan.FullRender(200, 60))
.ExpectChildMeasure("content", new Size(400, 200), SpacePlan.FullRender(300, 100))
.ExpectCanvasTranslate(0, 0)
.ExpectChildDraw("before", new Size(300, 40))
.ExpectCanvasTranslate(0, 0)
.ExpectCanvasTranslate(0, 40)
.ExpectChildDraw("content", new Size(300, 100))
.ExpectCanvasTranslate(0, -40)
.ExpectCanvasTranslate(0, 140)
.ExpectChildDraw("after", new Size(300, 60))
.ExpectCanvasTranslate(0, -140)
.ExpectChildMeasure("decoration", new Size(400, 300), SpacePlan.FullRender(300, 100))
.ExpectChildDraw("content", new Size(400, 200))
.ExpectCanvasTranslate(0, 200)
.ExpectChildDraw("decoration", new Size(400, 100))
.ExpectCanvasTranslate(0, -200)
.CheckDrawResult();
}
+1 -1
View File
@@ -8,7 +8,7 @@ namespace QuestPDF.UnitTests
public class ExternalLinkTests
{
[Test]
public void Measure() => SimpleContainerTests.Measure<Hyperlink>();
public void Measure() => SimpleContainerTests.Measure<ExternalLink>();
// TODO: consider tests for the Draw method
}
-146
View File
@@ -1,146 +0,0 @@
using FluentAssertions;
using NUnit.Framework;
using QuestPDF.Drawing;
using SkiaSharp;
using static SkiaSharp.SKFontStyleSlant;
namespace QuestPDF.UnitTests
{
[TestFixture]
public class FontStyleSetTests
{
private void ExpectComparisonOrder(SKFontStyle target, SKFontStyle[] styles)
{
for (var i = 0; i < styles.Length - 1; i++)
{
var currentStyle = styles[i];
var nextStyle = styles[i + 1];
FontStyleSet.IsBetterMatch(target, currentStyle, nextStyle).Should().BeTrue();
FontStyleSet.IsBetterMatch(target, nextStyle, currentStyle).Should().BeFalse();
}
}
[Test]
public void FontStyleSet_IsBetterMatch_CondensedWidth()
{
var styles = new[]
{
new SKFontStyle(500, 5, Upright),
new SKFontStyle(500, 4, Upright),
new SKFontStyle(500, 3, Upright),
new SKFontStyle(500, 6, Upright)
};
ExpectComparisonOrder(new SKFontStyle(500, 5, Upright), styles);
}
[Test]
public void FontStyleSet_IsBetterMatch_ExpandedWidth()
{
var styles = new[]
{
new SKFontStyle(500, 6, Upright),
new SKFontStyle(500, 7, Upright),
new SKFontStyle(500, 8, Upright),
new SKFontStyle(500, 5, Upright)
};
ExpectComparisonOrder(new SKFontStyle(500, 6, Upright), styles);
}
[Test]
public void FontStyleSet_IsBetterMatch_ItalicSlant()
{
var styles = new[]
{
new SKFontStyle(500, 5, Italic),
new SKFontStyle(500, 5, Oblique),
new SKFontStyle(500, 5, Upright)
};
ExpectComparisonOrder(new SKFontStyle(500, 5, Italic), styles);
}
[Test]
public void FontStyleSet_IsBetterMatch_ObliqueSlant()
{
var styles = new[]
{
new SKFontStyle(500, 5, Oblique),
new SKFontStyle(500, 5, Italic),
new SKFontStyle(500, 5, Upright)
};
ExpectComparisonOrder(new SKFontStyle(500, 5, Oblique), styles);
}
[Test]
public void FontStyleSet_IsBetterMatch_UprightSlant()
{
var styles = new[]
{
new SKFontStyle(500, 5, Upright),
new SKFontStyle(500, 5, Oblique),
new SKFontStyle(500, 5, Italic)
};
ExpectComparisonOrder(new SKFontStyle(500, 5, Upright), styles);
}
[Test]
public void FontStyleSet_IsBetterMatch_ThinWeight()
{
var styles = new[]
{
new SKFontStyle(300, 5, Upright),
new SKFontStyle(200, 5, Upright),
new SKFontStyle(100, 5, Upright),
new SKFontStyle(400, 5, Upright)
};
ExpectComparisonOrder(new SKFontStyle(300, 5, Upright), styles);
}
[Test]
public void FontStyleSet_IsBetterMatch_RegularWeight()
{
var styles = new[]
{
new SKFontStyle(500, 5, Upright),
new SKFontStyle(300, 5, Upright),
new SKFontStyle(100, 5, Upright),
new SKFontStyle(600, 5, Upright)
};
ExpectComparisonOrder(new SKFontStyle(400, 5, Upright), styles);
}
[Test]
public void FontStyleSet_IsBetterMatch_BoldWeight()
{
var styles = new[]
{
new SKFontStyle(600, 5, Upright),
new SKFontStyle(700, 5, Upright),
new SKFontStyle(800, 5, Upright),
new SKFontStyle(500, 5, Upright)
};
ExpectComparisonOrder(new SKFontStyle(600, 5, Upright), styles);
}
[Test]
public void FontStyleSet_RespectsPriority()
{
var styles = new[]
{
new SKFontStyle(600, 5, Italic),
new SKFontStyle(600, 6, Upright),
new SKFontStyle(500, 6, Italic)
};
ExpectComparisonOrder(new SKFontStyle(500, 5, Upright), styles);
}
}
}
+54 -54
View File
@@ -39,26 +39,26 @@ namespace QuestPDF.UnitTests
// assert
var expected = new Container();
expected.Column(column =>
expected.Stack(stack =>
{
column.Item().Row(row =>
stack.Item().Row(row =>
{
row.RelativeItem(6).Element(childA);
row.RelativeItem(4).Element(childB);
row.RelativeItem(2);
row.RelativeColumn(6).Element(childA);
row.RelativeColumn(4).Element(childB);
row.RelativeColumn(2);
});
column.Item().Row(row =>
stack.Item().Row(row =>
{
row.RelativeItem(4).Element(childC);
row.RelativeItem(2).Element(childD);
row.RelativeItem(6);
row.RelativeColumn(4).Element(childC);
row.RelativeColumn(2).Element(childD);
row.RelativeColumn(6);
});
column.Item().Row(row =>
stack.Item().Row(row =>
{
row.RelativeItem(8).Element(childE);
row.RelativeItem(4);
row.RelativeColumn(8).Element(childE);
row.RelativeColumn(4);
});
});
@@ -93,29 +93,29 @@ namespace QuestPDF.UnitTests
// assert
var expected = new Container();
expected.Column(column =>
expected.Stack(stack =>
{
column.Item().Row(row =>
stack.Item().Row(row =>
{
row.RelativeItem(1);
row.RelativeItem(6).Element(childA);
row.RelativeItem(4).Element(childB);
row.RelativeItem(1);
row.RelativeColumn(1);
row.RelativeColumn(6).Element(childA);
row.RelativeColumn(4).Element(childB);
row.RelativeColumn(1);
});
column.Item().Row(row =>
stack.Item().Row(row =>
{
row.RelativeItem(3);
row.RelativeItem(4).Element(childC);
row.RelativeItem(2).Element(childD);
row.RelativeItem(3);
row.RelativeColumn(3);
row.RelativeColumn(4).Element(childC);
row.RelativeColumn(2).Element(childD);
row.RelativeColumn(3);
});
column.Item().Row(row =>
stack.Item().Row(row =>
{
row.RelativeItem(2);
row.RelativeItem(8).Element(childE);
row.RelativeItem(2);
row.RelativeColumn(2);
row.RelativeColumn(8).Element(childE);
row.RelativeColumn(2);
});
});
@@ -150,26 +150,26 @@ namespace QuestPDF.UnitTests
// assert
var expected = new Container();
expected.Column(column =>
expected.Stack(stack =>
{
column.Item().Row(row =>
stack.Item().Row(row =>
{
row.RelativeItem(2);
row.RelativeItem(6).Element(childA);
row.RelativeItem(4).Element(childB);
row.RelativeColumn(2);
row.RelativeColumn(6).Element(childA);
row.RelativeColumn(4).Element(childB);
});
column.Item().Row(row =>
stack.Item().Row(row =>
{
row.RelativeItem(6);
row.RelativeItem(4).Element(childC);
row.RelativeItem(2).Element(childD);
row.RelativeColumn(6);
row.RelativeColumn(4).Element(childC);
row.RelativeColumn(2).Element(childD);
});
column.Item().Row(row =>
stack.Item().Row(row =>
{
row.RelativeItem(4);
row.RelativeItem(8).Element(childE);
row.RelativeColumn(4);
row.RelativeColumn(8).Element(childE);
});
});
@@ -210,36 +210,36 @@ namespace QuestPDF.UnitTests
// assert
var expected = new Container();
expected.Column(column =>
expected.Stack(stack =>
{
column.Spacing(20);
stack.Spacing(20);
column.Item().Row(row =>
stack.Item().Row(row =>
{
row.Spacing(30);
row.RelativeItem(3);
row.RelativeItem(5).Element(childA);
row.RelativeItem(5).Element(childB);
row.RelativeItem(3);
row.RelativeColumn(3);
row.RelativeColumn(5).Element(childA);
row.RelativeColumn(5).Element(childB);
row.RelativeColumn(3);
});
column.Item().Row(row =>
stack.Item().Row(row =>
{
row.Spacing(30);
row.RelativeItem(3);
row.RelativeItem(10).Element(childC);
row.RelativeItem(3);
row.RelativeColumn(3);
row.RelativeColumn(10).Element(childC);
row.RelativeColumn(3);
});
column.Item().Row(row =>
stack.Item().Row(row =>
{
row.Spacing(30);
row.RelativeItem(2);
row.RelativeItem(12).Element(childD);
row.RelativeItem(2);
row.RelativeColumn(2);
row.RelativeColumn(12).Element(childD);
row.RelativeColumn(2);
});
});
+1 -1
View File
@@ -8,7 +8,7 @@ namespace QuestPDF.UnitTests
public class InternalLinkTests
{
[Test]
public void Measure() => SimpleContainerTests.Measure<SectionLink>();
public void Measure() => SimpleContainerTests.Measure<InternalLink>();
// TODO: consider tests for the Draw method
}
+1 -1
View File
@@ -8,7 +8,7 @@ namespace QuestPDF.UnitTests
public class InternalLocationTests
{
[Test]
public void Measure() => SimpleContainerTests.Measure<SectionLink>();
public void Measure() => SimpleContainerTests.Measure<InternalLink>();
// TODO: consider tests for the Draw method
}
+267
View File
@@ -0,0 +1,267 @@
using NUnit.Framework;
using QuestPDF.Drawing;
using QuestPDF.Elements;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
using QuestPDF.UnitTests.TestEngine;
namespace QuestPDF.UnitTests
{
[TestFixture]
public class RowTests
{
#region Measure
[Test]
public void Measure_ReturnsWrap_WhenLeftChildReturnsWrap()
{
TestPlan
.For(x => new BinaryRow
{
Left = x.CreateChild("left"),
Right = x.CreateChild("right")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("left", new Size(400, 300), SpacePlan.Wrap())
.CheckMeasureResult(SpacePlan.Wrap());
}
[Test]
public void Measure_ReturnsWrap_WhenRightChildReturnsWrap()
{
TestPlan
.For(x => new BinaryRow
{
Left = x.CreateChild("left"),
Right = x.CreateChild("right")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("left", new Size(400, 300), SpacePlan.FullRender(250, 150))
.ExpectChildMeasure("right", new Size(150, 300), SpacePlan.Wrap())
.CheckMeasureResult(SpacePlan.Wrap());
}
[Test]
public void Measure_ReturnsPartialRender_WhenLeftChildReturnsPartialRender()
{
TestPlan
.For(x => new BinaryRow
{
Left = x.CreateChild("left"),
Right = x.CreateChild("right")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("left", new Size(400, 300), SpacePlan.PartialRender(250, 150))
.ExpectChildMeasure("right", new Size(150, 300), SpacePlan.FullRender(100, 100))
.CheckMeasureResult(SpacePlan.PartialRender(350, 150));
}
[Test]
public void Measure_ReturnsPartialRender_WhenRightChildReturnsPartialRender()
{
TestPlan
.For(x => new BinaryRow
{
Left = x.CreateChild("left"),
Right = x.CreateChild("right")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("left", new Size(400, 300), SpacePlan.FullRender(250, 150))
.ExpectChildMeasure("right", new Size(150, 300), SpacePlan.PartialRender(100, 100))
.CheckMeasureResult(SpacePlan.PartialRender(350, 150));
}
[Test]
public void Measure_ReturnsFullRender_WhenBothChildrenReturnFullRender()
{
TestPlan
.For(x => new BinaryRow
{
Left = x.CreateChild("left"),
Right = x.CreateChild("right")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("left", new Size(400, 300), SpacePlan.FullRender(200, 150))
.ExpectChildMeasure("right", new Size(200, 300), SpacePlan.FullRender(100, 100))
.CheckMeasureResult(SpacePlan.FullRender(300, 150));
}
#endregion
#region Draw
[Test]
public void Draw()
{
TestPlan
.For(x => new BinaryRow
{
Left = x.CreateChild("left"),
Right = x.CreateChild("right")
})
.DrawElement(new Size(400, 300))
.ExpectChildMeasure("left", new Size(400, 300), SpacePlan.FullRender(250, 150))
.ExpectChildDraw("left", new Size(250, 300))
.ExpectCanvasTranslate(250, 0)
.ExpectChildDraw("right", new Size(150, 300))
.ExpectCanvasTranslate(-250, 0)
.CheckDrawResult();
}
#endregion
#region Structure
[Test]
public void Structure_RelativeColumnsHandling()
{
// arrange
var childA = TestPlan.CreateUniqueElement();
var childB = TestPlan.CreateUniqueElement();
var childC = TestPlan.CreateUniqueElement();
var childD = TestPlan.CreateUniqueElement();
var childE = TestPlan.CreateUniqueElement();
const int spacing = 25;
var availableSpace = new Size(1100, 400);
// act
var value = new Container();
value.Row(row =>
{
row.Spacing(spacing);
row.ConstantColumn(150).Element(childA);
row.ConstantColumn(250).Element(childB);
row.RelativeColumn(1).Element(childC);
row.RelativeColumn(2).Element(childD);
row.RelativeColumn(3).Element(childE);
});
// assert
var expected = new Container();
expected.Row(row =>
{
row.Spacing(spacing);
row.ConstantColumn(150).Element(childA);
row.ConstantColumn(250).Element(childB);
row.ConstantColumn(100).Element(childC);
row.ConstantColumn(200).Element(childD);
row.ConstantColumn(300).Element(childE);
});
TestPlan.CompareOperations(value, expected, availableSpace);
}
[Test]
public void Structure_Tree()
{
// arrange
var childA = TestPlan.CreateUniqueElement();
var childB = TestPlan.CreateUniqueElement();
var childC = TestPlan.CreateUniqueElement();
var childD = TestPlan.CreateUniqueElement();
var childE = TestPlan.CreateUniqueElement();
const int spacing = 25;
var availableSpace = new Size(1200, 400);
// act
var value = new Container();
value.Row(row =>
{
row.Spacing(spacing);
row.ConstantColumn(150).Element(childA);
row.ConstantColumn(200).Element(childB);
row.ConstantColumn(250).Element(childC);
row.RelativeColumn(2).Element(childD);
row.RelativeColumn(3).Element(childE);
});
// assert
var expected = new BinaryRow
{
Left = new BinaryRow
{
Left = new BinaryRow
{
Left = new Constrained
{
MinWidth = 150,
MaxWidth = 150,
Child = childA
},
Right = new Constrained
{
MinWidth = 25,
MaxWidth = 25
}
},
Right = new BinaryRow
{
Left = new Constrained
{
MinWidth = 200,
MaxWidth = 200,
Child = childB
},
Right = new Constrained
{
MinWidth = 25,
MaxWidth = 25
}
}
},
Right = new BinaryRow
{
Left = new BinaryRow
{
Left = new Constrained
{
MinWidth = 250,
MaxWidth = 250,
Child = childC
},
Right = new Constrained
{
MinWidth = 25,
MaxWidth = 25
}
},
Right = new BinaryRow
{
Left = new Constrained
{
MinWidth = 200,
MaxWidth = 200,
Child = childD
},
Right = new BinaryRow
{
Left = new Constrained
{
MinWidth = 25,
MaxWidth = 25
},
Right = new Constrained
{
MinWidth = 300,
MaxWidth = 300,
Child = childE
}
}
}
}
};
TestPlan.CompareOperations(value, expected, availableSpace);
}
#endregion
}
}
@@ -1,5 +1,4 @@
using System.Linq;
using NUnit.Framework;
using NUnit.Framework;
using QuestPDF.Drawing;
using QuestPDF.Elements;
using QuestPDF.Fluent;
@@ -9,40 +8,19 @@ using QuestPDF.UnitTests.TestEngine;
namespace QuestPDF.UnitTests
{
[TestFixture]
public class ColumnTests
public class StackTests
{
private Column CreateColumnWithTwoItems(TestPlan testPlan)
{
return new Column
{
Items =
{
new ColumnItem
{
Child = testPlan.CreateChild("first")
},
new ColumnItem
{
Child = testPlan.CreateChild("second")
}
}
};
}
private Column CreateColumnWithTwoItemsWhereFirstIsFullyRendered(TestPlan testPlan)
{
var column = CreateColumnWithTwoItems(testPlan);
column.Items.First().IsRendered = true;
return column;
}
#region Measure
[Test]
public void Measure_ReturnsWrap_WhenFirstChildWraps()
{
TestPlan
.For(CreateColumnWithTwoItems)
.For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.Wrap())
.CheckMeasureResult(SpacePlan.Wrap());
@@ -52,7 +30,11 @@ namespace QuestPDF.UnitTests
public void Measure_ReturnsPartialRender_WhenFirstChildReturnsPartialRender()
{
TestPlan
.For(CreateColumnWithTwoItems)
.For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.PartialRender(300, 200))
.CheckMeasureResult(SpacePlan.PartialRender(300, 200));
@@ -62,7 +44,11 @@ namespace QuestPDF.UnitTests
public void Measure_ReturnsPartialRender_WhenSecondChildWraps()
{
TestPlan
.For(CreateColumnWithTwoItems)
.For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100))
.ExpectChildMeasure("second", new Size(400, 200), SpacePlan.Wrap())
@@ -73,7 +59,11 @@ namespace QuestPDF.UnitTests
public void Measure_ReturnsPartialRender_WhenSecondChildReturnsPartialRender()
{
TestPlan
.For(CreateColumnWithTwoItems)
.For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100))
.ExpectChildMeasure("second", new Size(400, 200), SpacePlan.PartialRender(300, 150))
@@ -84,13 +74,33 @@ namespace QuestPDF.UnitTests
public void Measure_ReturnsFullRender_WhenSecondChildReturnsFullRender()
{
TestPlan
.For(CreateColumnWithTwoItems)
.For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100))
.ExpectChildMeasure("second", new Size(400, 200), SpacePlan.FullRender(100, 50))
.CheckMeasureResult(SpacePlan.FullRender(200, 150));
}
[Test]
public void Measure_UsesEmpty_WhenFirstChildIsRendered()
{
TestPlan
.For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second"),
IsFirstRendered = true
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("second", new Size(400, 300), SpacePlan.FullRender(200, 300))
.CheckMeasureResult(SpacePlan.FullRender(200, 300));
}
#endregion
#region Draw
@@ -99,7 +109,11 @@ namespace QuestPDF.UnitTests
public void Draw_WhenFirstChildWraps()
{
TestPlan
.For(CreateColumnWithTwoItems)
.For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second")
})
.DrawElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.Wrap())
.CheckDrawResult();
@@ -109,12 +123,14 @@ namespace QuestPDF.UnitTests
public void Draw_WhenFirstChildPartiallyRenders()
{
TestPlan
.For(CreateColumnWithTwoItems)
.For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second")
})
.DrawElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.PartialRender(200, 100))
.ExpectCanvasTranslate(0, 0)
.ExpectChildDraw("first", new Size(400, 100))
.ExpectCanvasTranslate(0, 0)
.CheckDrawResult();
}
@@ -122,13 +138,15 @@ namespace QuestPDF.UnitTests
public void Draw_WhenFirstChildFullyRenders_AndSecondChildWraps()
{
TestPlan
.For(CreateColumnWithTwoItems)
.For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second")
})
.DrawElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100))
.ExpectChildMeasure("second", new Size(400, 200), SpacePlan.Wrap())
.ExpectCanvasTranslate(0, 0)
.ExpectChildDraw("first", new Size(400, 100))
.ExpectCanvasTranslate(0, 0)
.ExpectChildMeasure("second", new Size(400, 200), SpacePlan.Wrap())
.CheckDrawResult();
}
@@ -136,13 +154,15 @@ namespace QuestPDF.UnitTests
public void Draw_WhenFirstChildFullyRenders_AndSecondChildPartiallyRenders()
{
TestPlan
.For(CreateColumnWithTwoItems)
.For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second")
})
.DrawElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100))
.ExpectChildMeasure("second", new Size(400, 200), SpacePlan.PartialRender(250, 150))
.ExpectCanvasTranslate(0, 0)
.ExpectChildDraw("first", new Size(400, 100))
.ExpectCanvasTranslate(0, 0)
.ExpectChildMeasure("second", new Size(400, 200), SpacePlan.PartialRender(250, 150))
.ExpectCanvasTranslate(0, 100)
.ExpectChildDraw("second", new Size(400, 150))
.ExpectCanvasTranslate(0, -100)
@@ -153,13 +173,15 @@ namespace QuestPDF.UnitTests
public void Draw_WhenFirstChildFullyRenders_AndSecondChildFullyRenders()
{
TestPlan
.For(CreateColumnWithTwoItems)
.For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second")
})
.DrawElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100))
.ExpectChildMeasure("second", new Size(400, 200), SpacePlan.FullRender(250, 150))
.ExpectCanvasTranslate(0, 0)
.ExpectChildDraw("first", new Size(400, 100))
.ExpectCanvasTranslate(0, 0)
.ExpectChildMeasure("second", new Size(400, 200), SpacePlan.FullRender(250, 150))
.ExpectCanvasTranslate(0, 100)
.ExpectChildDraw("second", new Size(400, 150))
.ExpectCanvasTranslate(0, -100)
@@ -170,13 +192,19 @@ namespace QuestPDF.UnitTests
public void Draw_UsesEmpty_WhenFirstChildIsRendered()
{
TestPlan
.For(CreateColumnWithTwoItemsWhereFirstIsFullyRendered)
.For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second"),
IsFirstRendered = true
})
.DrawElement(new Size(400, 300))
.ExpectChildMeasure("second", new Size(400, 300), SpacePlan.PartialRender(200, 300))
.ExpectCanvasTranslate(0, 0)
.ExpectChildDraw("second", new Size(400, 300))
.ExpectCanvasTranslate(0, 0)
.CheckState<Column>(x => x.Items.First().IsRendered)
.CheckState<BinaryStack>(x => x.IsFirstRendered)
.CheckDrawResult();
}
@@ -184,14 +212,97 @@ namespace QuestPDF.UnitTests
public void Draw_TogglesFirstRenderedFlag_WhenSecondFullyRenders()
{
TestPlan
.For(CreateColumnWithTwoItemsWhereFirstIsFullyRendered)
.For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second"),
IsFirstRendered = true
})
.DrawElement(new Size(400, 300))
.ExpectChildMeasure("second", new Size(400, 300), SpacePlan.FullRender(200, 300))
.ExpectCanvasTranslate(0, 0)
.ExpectChildDraw("second", new Size(400, 300))
.ExpectCanvasTranslate(0, 0)
.CheckDrawResult()
.CheckState<Column>(x => !x.Items.First().IsRendered);
.CheckState<BinaryStack>(x => !x.IsFirstRendered);
}
#endregion
#region Structure
[Test]
public void Structure_Simple()
{
// arrange
var childA = TestPlan.CreateUniqueElement();
var childB = TestPlan.CreateUniqueElement();
var childC = TestPlan.CreateUniqueElement();
var childD = TestPlan.CreateUniqueElement();
var childE = TestPlan.CreateUniqueElement();
const int spacing = 20;
// act
var structure = new Container();
structure.Stack(stack =>
{
stack.Spacing(spacing);
stack.Item().Element(childA);
stack.Item().Element(childB);
stack.Item().Element(childC);
stack.Item().Element(childD);
stack.Item().Element(childE);
});
// assert
var expected = new Padding
{
Bottom = -spacing,
Child = new BinaryStack
{
First = new BinaryStack
{
First = new Padding
{
Bottom = spacing,
Child = childA
},
Second = new Padding
{
Bottom = spacing,
Child = childB
}
},
Second = new BinaryStack
{
First = new Padding
{
Bottom = spacing,
Child = childC
},
Second = new BinaryStack
{
First = new Padding
{
Bottom = spacing,
Child = childD
},
Second = new Padding
{
Bottom = spacing,
Child = childE
}
}
}
}
};
TestPlan.CompareOperations(structure, expected);
}
#endregion
+8 -7
View File
@@ -11,19 +11,20 @@ namespace QuestPDF.UnitTests.TestEngine
public Action<float, float> ScaleFunc { get; set; }
public Action<SKImage, Position, Size> DrawImageFunc { get; set; }
public Action<string, Position, TextStyle> DrawTextFunc { get; set; }
public Action<Position, Size, string> DrawRectFunc { get; set; }
public Action<Position, Size, string> DrawFilledRectangleFunc { get; set; }
public Action<Size, string, float> DrawStrokedRectangleFunc { get; set; }
public void Translate(Position vector) => TranslateFunc(vector);
public void Rotate(float angle) => RotateFunc(angle);
public void Scale(float scaleX, float scaleY) => ScaleFunc(scaleX, scaleY);
public void DrawRectangle(Position vector, Size size, string color) => DrawRectFunc(vector, size, color);
public void DrawFilledRectangle(Position vector, Size size, string color) => DrawFilledRectangleFunc(vector, size, color);
public void DrawStrokedRectangle(Size size, string color, float width) => DrawStrokedRectangleFunc(size, color, width);
public void DrawText(string text, Position position, TextStyle style) => DrawTextFunc(text, position, style);
public void DrawShapedText(string text, Position position, TextStyle style) => DrawTextFunc(text, position, style);
public void DrawImage(SKImage image, Position position, Size size) => DrawImageFunc(image, position, size);
public void DrawHyperlink(string url, Size size) => throw new NotImplementedException();
public void DrawSectionLink(string sectionName, Size size) => throw new NotImplementedException();
public void DrawSection(string sectionName) => throw new NotImplementedException();
public void DrawExternalLink(string url, Size size) => throw new NotImplementedException();
public void DrawLocationLink(string locationName, Size size) => throw new NotImplementedException();
public void DrawLocation(string locationName) => throw new NotImplementedException();
}
}
@@ -14,13 +14,13 @@ namespace QuestPDF.UnitTests.TestEngine
public void Rotate(float angle) => Operations.Add(new CanvasRotateOperation(angle));
public void Scale(float scaleX, float scaleY) => Operations.Add(new CanvasScaleOperation(scaleX, scaleY));
public void DrawRectangle(Position vector, Size size, string color) => Operations.Add(new CanvasDrawRectangleOperation(vector, size, color));
public void DrawFilledRectangle(Position vector, Size size, string color) => Operations.Add(new CanvasDrawFilledRectangleOperation(vector, size, color));
public void DrawStrokedRectangle(Size size, string color, float width) => Operations.Add(new CanvasDrawStrokedRectangleOperation(size, color, width));
public void DrawText(string text, Position position, TextStyle style) => Operations.Add(new CanvasDrawTextOperation(text, position, style));
public void DrawShapedText(string text, Position position, TextStyle style) => Operations.Add(new CanvasDrawTextOperation(text, position, style));
public void DrawImage(SKImage image, Position position, Size size) => Operations.Add(new CanvasDrawImageOperation(position, size));
public void DrawHyperlink(string url, Size size) => throw new NotImplementedException();
public void DrawSectionLink(string sectionName, Size size) => throw new NotImplementedException();
public void DrawSection(string sectionName) => throw new NotImplementedException();
public void DrawExternalLink(string url, Size size) => throw new NotImplementedException();
public void DrawLocationLink(string locationName, Size size) => throw new NotImplementedException();
public void DrawLocation(string locationName) => throw new NotImplementedException();
}
}
@@ -2,13 +2,13 @@
namespace QuestPDF.UnitTests.TestEngine.Operations
{
internal class CanvasDrawRectangleOperation : OperationBase
internal class CanvasDrawFilledRectangleOperation : OperationBase
{
public Position Position { get; }
public Size Size { get; }
public string Color { get; }
public CanvasDrawRectangleOperation(Position position, Size size, string color)
public CanvasDrawFilledRectangleOperation(Position position, Size size, string color)
{
Position = position;
Size = size;
@@ -0,0 +1,18 @@
using QuestPDF.Infrastructure;
namespace QuestPDF.UnitTests.TestEngine.Operations
{
internal class CanvasDrawStrokedRectangleOperation : OperationBase
{
public Size Size { get; }
public string Color { get; }
public float Width { get; set; }
public CanvasDrawStrokedRectangleOperation(Size size, string color, float width)
{
Size = size;
Color = color;
Width = width;
}
}
}
+30 -15
View File
@@ -70,17 +70,27 @@ namespace QuestPDF.UnitTests.TestEngine
Assert.AreEqual(expected.ScaleX, scaleX, "Scale X");
Assert.AreEqual(expected.ScaleY, scaleY, "Scale Y");
},
DrawRectFunc = (position, size, color) =>
DrawFilledRectangleFunc = (position, size, color) =>
{
var expected = GetExpected<CanvasDrawRectangleOperation>();
var expected = GetExpected<CanvasDrawFilledRectangleOperation>();
Assert.AreEqual(expected.Position.X, position.X, "Draw rectangle: X");
Assert.AreEqual(expected.Position.Y, position.Y, "Draw rectangle: Y");
Assert.AreEqual(expected.Position.X, position.X, "Draw filled rectangle: X");
Assert.AreEqual(expected.Position.Y, position.Y, "Draw filled rectangle: Y");
Assert.AreEqual(expected.Size.Width, size.Width, "Draw rectangle: width");
Assert.AreEqual(expected.Size.Height, size.Height, "Draw rectangle: height");
Assert.AreEqual(expected.Size.Width, size.Width, "Draw filled rectangle: width");
Assert.AreEqual(expected.Size.Height, size.Height, "Draw filled rectangle: height");
Assert.AreEqual(expected.Color, color, "Draw rectangle: color");
Assert.AreEqual(expected.Color, color, "Draw filled rectangle: color");
},
DrawStrokedRectangleFunc = (size, color, width) =>
{
var expected = GetExpected<CanvasDrawStrokedRectangleOperation>();
Assert.AreEqual(expected.Size.Width, size.Width, "Draw stroked rectangle: width");
Assert.AreEqual(expected.Size.Height, size.Height, "Draw stroked rectangle: height");
Assert.AreEqual(expected.Color, color, "Draw stroked rectangle: color");
Assert.AreEqual(expected.Width, width, "Draw stroked rectangle: width");
},
DrawTextFunc = (text, position, style) =>
{
@@ -196,9 +206,14 @@ namespace QuestPDF.UnitTests.TestEngine
return AddOperation(new CanvasRotateOperation(angle));
}
public TestPlan ExpectCanvasDrawRectangle(Position position, Size size, string color)
public TestPlan ExpectCanvasDrawFilledRectangle(Position position, Size size, string color)
{
return AddOperation(new CanvasDrawRectangleOperation(position, size, color));
return AddOperation(new CanvasDrawFilledRectangleOperation(position, size, color));
}
public TestPlan ExpectCanvasDrawStrokedRectangle(Size size, string color, float width)
{
return AddOperation(new CanvasDrawStrokedRectangleOperation(size, color, width));
}
public TestPlan ExpectCanvasDrawText(string text, Position position, TextStyle style)
@@ -213,7 +228,7 @@ namespace QuestPDF.UnitTests.TestEngine
public TestPlan CheckMeasureResult(SpacePlan expected)
{
Element.VisitChildren(x => x?.Initialize(null, Canvas));
Element.HandleVisitor(x => x?.Initialize(null, Canvas));
var actual = Element.Measure(OperationInput);
@@ -228,7 +243,7 @@ namespace QuestPDF.UnitTests.TestEngine
public TestPlan CheckDrawResult()
{
Element.VisitChildren(x => x?.Initialize(null, Canvas));
Element.HandleVisitor(x => x?.Initialize(null, Canvas));
Element.Draw(OperationInput);
return this;
}
@@ -271,10 +286,10 @@ namespace QuestPDF.UnitTests.TestEngine
availableSpace ??= new Size(400, 300);
var canvas = new FreeCanvas();
value.VisitChildren(x => x.Initialize(null, canvas));
value.HandleVisitor(x => x.Initialize(null, canvas));
var valueMeasure = value.Measure(availableSpace.Value);
expected.VisitChildren(x => x.Initialize(null, canvas));
expected.HandleVisitor(x => x.Initialize(null, canvas));
var expectedMeasure = expected.Measure(availableSpace.Value);
valueMeasure.Should().BeEquivalentTo(expectedMeasure);
@@ -285,11 +300,11 @@ namespace QuestPDF.UnitTests.TestEngine
availableSpace ??= new Size(400, 300);
var valueCanvas = new OperationRecordingCanvas();
value.VisitChildren(x => x.Initialize(null, valueCanvas));
value.HandleVisitor(x => x.Initialize(null, valueCanvas));
value.Draw(availableSpace.Value);
var expectedCanvas = new OperationRecordingCanvas();
expected.VisitChildren(x => x.Initialize(null, expectedCanvas));
expected.HandleVisitor(x => x.Initialize(null, expectedCanvas));
expected.Draw(availableSpace.Value);
valueCanvas.Operations.Should().BeEquivalentTo(expectedCanvas.Operations);
+3 -3
View File
@@ -16,13 +16,13 @@ namespace QuestPDF.Drawing
var container = new Container();
container
.Column(column =>
.Stack(stack =>
{
Pages
.SelectMany(x => new List<Action>()
{
() => column.Item().PageBreak(),
() => column.Item().Component(x)
() => stack.Item().PageBreak(),
() => stack.Item().Component(x)
})
.Skip(1)
.ToList()
+21 -38
View File
@@ -7,8 +7,6 @@ using QuestPDF.Drawing.Proxy;
using QuestPDF.Elements;
using QuestPDF.Elements.Text;
using QuestPDF.Elements.Text.Items;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Drawing
@@ -18,16 +16,14 @@ namespace QuestPDF.Drawing
internal static void GeneratePdf(Stream stream, IDocument document)
{
var metadata = document.GetMetadata();
var writeOnlyStream = new WriteOnlyStream(stream);
var canvas = new PdfCanvas(writeOnlyStream, metadata);
var canvas = new PdfCanvas(stream, metadata);
RenderDocument(canvas, document);
}
internal static void GenerateXps(Stream stream, IDocument document)
{
var metadata = document.GetMetadata();
var writeOnlyStream = new WriteOnlyStream(stream);
var canvas = new XpsCanvas(writeOnlyStream, metadata);
var canvas = new XpsCanvas(stream, metadata);
RenderDocument(canvas, document);
}
@@ -46,7 +42,6 @@ namespace QuestPDF.Drawing
var container = new DocumentContainer();
document.Compose(container);
var content = container.Compose();
ApplyDefaultTextStyle(content, TextStyle.LibraryDefault);
var metadata = document.GetMetadata();
var pageContext = new PageContext();
@@ -63,8 +58,8 @@ namespace QuestPDF.Drawing
internal static void RenderPass<TCanvas>(PageContext pageContext, TCanvas canvas, Container content, DocumentMetadata documentMetadata, DebuggingState? debuggingState)
where TCanvas : ICanvas, IRenderingCanvas
{
content.VisitChildren(x => x?.Initialize(pageContext, canvas));
content.VisitChildren(x => (x as IStateResettable)?.ResetState());
content.HandleVisitor(x => x?.Initialize(pageContext, canvas));
content.HandleVisitor(x => (x as IStateResettable)?.ResetState());
canvas.BeginDocument();
@@ -127,7 +122,7 @@ namespace QuestPDF.Drawing
private static void ApplyCaching(Container content)
{
content.VisitChildren(x =>
content.HandleVisitor(x =>
{
if (x is ICacheable)
x.CreateProxy(y => new CacheProxy(y));
@@ -138,7 +133,7 @@ namespace QuestPDF.Drawing
{
var debuggingState = new DebuggingState();
content.VisitChildren(x =>
content.HandleVisitor(x =>
{
x.CreateProxy(y => new DebuggingProxy(debuggingState, y));
});
@@ -146,38 +141,26 @@ namespace QuestPDF.Drawing
return debuggingState;
}
private static void ApplyDefaultTextStyle(this Element? content, TextStyle documentDefaultTextStyle)
internal static void ApplyDefaultTextStyle(this Element content, TextStyle documentDefaultTextStyle)
{
if (content == null)
return;
documentDefaultTextStyle.ApplyGlobalStyle(TextStyle.LibraryDefault);
if (content is TextBlock textBlock)
content.HandleVisitor(element =>
{
foreach (var textBlockItem in textBlock.Items)
{
if (textBlockItem is TextBlockSpan textSpan)
{
textSpan.Style.ApplyGlobalStyle(documentDefaultTextStyle);
}
else if (textBlockItem is TextBlockElement textElement)
{
ApplyDefaultTextStyle(textElement.Element, documentDefaultTextStyle);
}
}
var text = element as TextBlock;
return;
}
if (text == null)
return;
var targetTextStyle = documentDefaultTextStyle;
if (content is DefaultTextStyle defaultTextStyleElement)
{
defaultTextStyleElement.TextStyle.ApplyParentStyle(documentDefaultTextStyle);
targetTextStyle = defaultTextStyleElement.TextStyle;
}
foreach (var child in content.GetChildren())
ApplyDefaultTextStyle(child, targetTextStyle);
foreach (var child in text.Children)
{
if (child is TextBlockSpan textSpan)
textSpan.Style.ApplyGlobalStyle(documentDefaultTextStyle);
if (child is TextBlockElement textElement)
ApplyDefaultTextStyle(textElement.Element, documentDefaultTextStyle);
}
});
}
}
}
+17 -66
View File
@@ -1,52 +1,23 @@
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Linq;
using QuestPDF.Infrastructure;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace QuestPDF.Drawing
{
public static class FontManager
{
private static ConcurrentDictionary<string, FontStyleSet> StyleSets = new();
private static ConcurrentDictionary<object, SKFontMetrics> FontMetrics = new();
private static ConcurrentDictionary<object, SKPaint> Paints = new();
private static ConcurrentDictionary<object, SKFont> Fonts = new();
private static ConcurrentDictionary<object, SKShaper> Shapers = new();
private static ConcurrentDictionary<string, SKPaint> ColorPaint = new();
private static ConcurrentDictionary<string, SKTypeface> Typefaces = new ConcurrentDictionary<string, SKTypeface>();
private static ConcurrentDictionary<string, SKFontMetrics> FontMetrics = new ConcurrentDictionary<string, SKFontMetrics>();
private static ConcurrentDictionary<string, SKPaint> Paints = new ConcurrentDictionary<string, SKPaint>();
private static ConcurrentDictionary<string, SKPaint> ColorPaint = new ConcurrentDictionary<string, SKPaint>();
private static void RegisterFontType(SKData fontData, string? customName = null)
{
foreach (var index in Enumerable.Range(0, 256))
{
var typeface = SKTypeface.FromData(fontData, index);
if (typeface == null)
break;
var typefaceName = customName ?? typeface.FamilyName;
var fontStyleSet = StyleSets.GetOrAdd(typefaceName, _ => new FontStyleSet());
fontStyleSet.Add(typeface);
}
}
[Obsolete("Since version 2022.3, the FontManager class offers better font type matching support. Please use the RegisterFontType(Stream stream) overload.")]
public static void RegisterFontType(string fontName, Stream stream)
{
using var fontData = SKData.Create(stream);
RegisterFontType(fontData);
RegisterFontType(fontData, customName: fontName);
Typefaces.TryAdd(fontName, SKTypeface.FromStream(stream));
}
public static void RegisterFontType(Stream stream)
{
using var fontData = SKData.Create(stream);
RegisterFontType(fontData);
}
internal static SKPaint ColorToPaint(this string color)
{
return ColorPaint.GetOrAdd(color, Convert);
@@ -59,57 +30,37 @@ namespace QuestPDF.Drawing
};
}
}
internal static SKPaint ToPaint(this TextStyle style)
{
return Paints.GetOrAdd(style.PaintKey, key => Convert(style));
return Paints.GetOrAdd(style.Key, key => Convert(style));
static SKPaint Convert(TextStyle style)
{
return new SKPaint
{
Color = SKColor.Parse(style.Color),
Typeface = GetTypeface(style),
TextSize = style.Size ?? 12
TextSize = (style.Size ?? 12),
TextEncoding = SKTextEncoding.Utf32
};
}
static SKTypeface GetTypeface(TextStyle style)
{
var weight = (SKFontStyleWeight)(style.FontWeight ?? FontWeight.Normal);
if (Typefaces.TryGetValue(style.FontType, out var result))
return result;
var slant = (style.IsItalic ?? false) ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright;
var fontStyle = new SKFontStyle(weight, SKFontStyleWidth.Normal, slant);
if (StyleSets.TryGetValue(style.FontType, out var fontStyleSet))
return fontStyleSet.Match(fontStyle);
var fontFromDefaultSource = SKFontManager.Default.MatchFamily(style.FontType, fontStyle);
if (fontFromDefaultSource != null)
return fontFromDefaultSource;
throw new ArgumentException(
$"The typeface '{style.FontType}' could not be found. " +
$"Please consider the following options: " +
$"1) install the font on your operating system or execution environment. " +
$"2) load a font file specifically for QuestPDF usage via the QuestPDF.Drawing.FontManager.RegisterFontType(Stream fileContentStream) static method.");
return SKTypeface.FromFamilyName(style.FontType, (int)(style.FontWeight ?? FontWeight.Normal), (int)SKFontStyleWidth.Normal, slant)
?? throw new ArgumentException($"The typeface {style.FontType} could not be found.");
}
}
internal static SKFont ToFont(this TextStyle style)
{
return Fonts.GetOrAdd(style.FontMetricsKey, _ => style.ToPaint().Typeface.ToFont());
}
internal static SKFontMetrics ToFontMetrics(this TextStyle style)
{
return FontMetrics.GetOrAdd(style.FontMetricsKey, key => style.ToPaint().FontMetrics);
}
internal static SKShaper ToShaper(this TextStyle style)
{
return Shapers.GetOrAdd(style.FontMetricsKey, _ => new SKShaper(style.ToFont().Typeface));
return FontMetrics.GetOrAdd(style.Key, key => style.ToPaint().FontMetrics);
}
}
}
-132
View File
@@ -1,132 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using SkiaSharp;
namespace QuestPDF.Drawing
{
internal class FontStyleSet
{
private ConcurrentDictionary<SKFontStyle, SKTypeface> Styles { get; } = new();
public void Add(SKTypeface typeface)
{
var style = typeface.FontStyle;
Styles.AddOrUpdate(style, _ => typeface, (_, _) => typeface);
}
public SKTypeface? Match(SKFontStyle target)
{
SKFontStyle? bestStyle = null;
SKTypeface? bestTypeface = null;
foreach (var entry in Styles)
{
if (IsBetterMatch(target, entry.Key, bestStyle))
{
bestStyle = entry.Key;
bestTypeface = entry.Value;
}
}
return bestTypeface;
}
private static Dictionary<SKFontStyleSlant, List<SKFontStyleSlant>> SlantFallbacks = new()
{
{ SKFontStyleSlant.Italic, new() { SKFontStyleSlant.Italic, SKFontStyleSlant.Oblique, SKFontStyleSlant.Upright } },
{ SKFontStyleSlant.Oblique, new() { SKFontStyleSlant.Oblique, SKFontStyleSlant.Italic, SKFontStyleSlant.Upright } },
{ SKFontStyleSlant.Upright, new() { SKFontStyleSlant.Upright, SKFontStyleSlant.Oblique, SKFontStyleSlant.Italic } },
};
// Checks whether style a is a better match for the target then style b. Uses the CSS font style matching algorithm
internal static bool IsBetterMatch(SKFontStyle? target, SKFontStyle? a, SKFontStyle? b)
{
// A font is better than no font
if (b == null)
return true;
if (a == null)
return false;
// First check font width
// For normal and condensed widths prefer smaller widths
// For expanded widths prefer larger widths
if (target.Width <= (int)SKFontStyleWidth.Normal)
{
if (a.Width <= target.Width && b.Width > target.Width)
return true;
if (a.Width > target.Width && b.Width <= target.Width)
return false;
}
else
{
if (a.Width >= target.Width && b.Width < target.Width)
return true;
if (a.Width < target.Width && b.Width >= target.Width)
return false;
}
// Prefer closest match
var widthDifferenceA = Math.Abs(a.Width - target.Width);
var widthDifferenceB = Math.Abs(b.Width - target.Width);
if (widthDifferenceA < widthDifferenceB)
return true;
if (widthDifferenceB < widthDifferenceA)
return false;
// Prefer closest slant based on provided fallback list
var slantFallback = SlantFallbacks[target.Slant];
var slantIndexA = slantFallback.IndexOf(a.Slant);
var slantIndexB = slantFallback.IndexOf(b.Slant);
if (slantIndexA < slantIndexB)
return true;
if (slantIndexB < slantIndexA)
return false;
// Check weight last
// For thin (<400) weights, prefer thinner weights
// For regular (400-500) weights, prefer other regular weights, then use rule for thin or bold
// For bold (>500) weights, prefer thicker weights
// Behavior for values other than multiples of 100 is not given in the specification
if (target.Weight >= 400 && target.Weight <= 500)
{
if ((a.Weight >= 400 && a.Weight <= 500) && !(b.Weight >= 400 && b.Weight <= 500))
return true;
if (!(a.Weight >= 400 && a.Weight <= 500) && (b.Weight >= 400 && b.Weight <= 500))
return false;
}
if (target.Weight < 450)
{
if (a.Weight <= target.Weight && b.Weight > target.Weight)
return true;
if (a.Weight > target.Weight && b.Weight <= target.Weight)
return false;
}
else
{
if (a.Weight >= target.Weight && b.Weight < target.Weight)
return true;
if (a.Weight < target.Weight && b.Weight >= target.Weight)
return false;
}
// Prefer closest weight
var weightDifferenceA = Math.Abs(a.Weight - target.Weight);
var weightDifferenceB = Math.Abs(b.Weight - target.Weight);
return weightDifferenceA < weightDifferenceB;
}
}
}
+9 -9
View File
@@ -36,7 +36,12 @@ namespace QuestPDF.Drawing
}
public void DrawRectangle(Position vector, Size size, string color)
public void DrawFilledRectangle(Position vector, Size size, string color)
{
}
public void DrawStrokedRectangle(Size size, string color, float width)
{
}
@@ -44,11 +49,6 @@ namespace QuestPDF.Drawing
public void DrawText(string text, Position position, TextStyle style)
{
}
public void DrawShapedText(string text, Position position, TextStyle style)
{
}
public void DrawImage(SKImage image, Position position, Size size)
@@ -56,17 +56,17 @@ namespace QuestPDF.Drawing
}
public void DrawHyperlink(string url, Size size)
public void DrawExternalLink(string url, Size size)
{
}
public void DrawSectionLink(string sectionName, Size size)
public void DrawLocationLink(string locationName, Size size)
{
}
public void DrawSection(string sectionName)
public void DrawLocation(string locationName)
{
}
+1 -1
View File
@@ -1,5 +1,5 @@
using System.IO;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using SkiaSharp;
namespace QuestPDF.Drawing
+41
View File
@@ -0,0 +1,41 @@
using System.Collections.Concurrent;
using SkiaSharp;
namespace QuestPDF.Drawing
{
internal class SkiaCache
{
private static ConcurrentDictionary<string, SKPaint> RectangleFillPaints = new ConcurrentDictionary<string, SKPaint>();
private static ConcurrentDictionary<string, SKPaint> RectangleStrokePaints = new ConcurrentDictionary<string, SKPaint>();
public static SKPaint GetRectangleFillPaint(string color)
{
return RectangleFillPaints.GetOrAdd(color, _ => Convert());
SKPaint Convert()
{
return new SKPaint
{
Color = SKColor.Parse(color),
Style = SKPaintStyle.Fill
};
}
}
public static SKPaint GetRectangleStrokePaint(string color, float width)
{
var key = $"{color}|{width}";
return RectangleStrokePaints.GetOrAdd(key, _ => Convert());
SKPaint Convert()
{
return new SKPaint
{
Color = SKColor.Parse(color),
Style = SKPaintStyle.Stroke,
StrokeWidth = width
};
}
}
}
}
+13 -13
View File
@@ -1,6 +1,5 @@
using QuestPDF.Infrastructure;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace QuestPDF.Drawing
{
@@ -19,43 +18,44 @@ namespace QuestPDF.Drawing
Canvas.Translate(vector.X, vector.Y);
}
public void DrawRectangle(Position vector, Size size, string color)
public void DrawFilledRectangle(Position vector, Size size, string color)
{
if (size.Width < Size.Epsilon || size.Height < Size.Epsilon)
return;
var paint = color.ColorToPaint();
var paint = SkiaCache.GetRectangleFillPaint(color);
Canvas.DrawRect(vector.X, vector.Y, size.Width, size.Height, paint);
}
public void DrawStrokedRectangle(Size size, string color, float width)
{
var paint = SkiaCache.GetRectangleStrokePaint(color, width);
Canvas.DrawRect(0, 0, size.Width, size.Height, paint);
}
public void DrawText(string text, Position vector, TextStyle style)
{
Canvas.DrawText(text, vector.X, vector.Y, style.ToPaint());
}
public void DrawShapedText(string text, Position vector, TextStyle style)
{
Canvas.DrawShapedText(text, vector.X, vector.Y, style.ToPaint());
}
public void DrawImage(SKImage image, Position vector, Size size)
{
Canvas.DrawImage(image, new SKRect(vector.X, vector.Y, size.Width, size.Height));
}
public void DrawHyperlink(string url, Size size)
public void DrawExternalLink(string url, Size size)
{
Canvas.DrawUrlAnnotation(new SKRect(0, 0, size.Width, size.Height), url);
}
public void DrawSectionLink(string sectionName, Size size)
public void DrawLocationLink(string locationName, Size size)
{
Canvas.DrawLinkDestinationAnnotation(new SKRect(0, 0, size.Width, size.Height), sectionName);
Canvas.DrawLinkDestinationAnnotation(new SKRect(0, 0, size.Width, size.Height), locationName);
}
public void DrawSection(string sectionName)
public void DrawLocation(string locationName)
{
Canvas.DrawNamedDestinationAnnotation(new SKPoint(0, 0), sectionName);
Canvas.DrawNamedDestinationAnnotation(new SKPoint(0, 0), locationName);
}
public void Rotate(float angle)
+1 -1
View File
@@ -31,7 +31,7 @@ namespace QuestPDF.Drawing
if (Type == SpacePlanType.Wrap)
return Type.ToString();
return $"{Type} (Width: {Width:N3}, Height: {Height:N3})";
return $"{Type} (Width: {Width:N2}, Height: {Height:N2})";
}
public static implicit operator Size(SpacePlan spacePlan)
+1 -1
View File
@@ -1,5 +1,5 @@
using System.IO;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using SkiaSharp;
namespace QuestPDF.Drawing
+1 -1
View File
@@ -9,7 +9,7 @@ namespace QuestPDF.Elements
internal override void Draw(Size availableSpace)
{
Canvas.DrawRectangle(Position.Zero, availableSpace, Color);
Canvas.DrawFilledRectangle(Position.Zero, availableSpace, Color);
base.Draw(availableSpace);
}
}
+42 -22
View File
@@ -1,3 +1,4 @@
using System;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
@@ -15,31 +16,50 @@ namespace QuestPDF.Elements
internal override void Draw(Size availableSpace)
{
base.Draw(availableSpace);
Canvas.DrawRectangle(
new Position(-Left/2, -Top/2),
new Size(availableSpace.Width + Left/2 + Right/2, Top),
Color);
Canvas.DrawRectangle(
new Position(-Left/2, -Top/2),
new Size(Left, availableSpace.Height + Top/2 + Bottom/2),
Color);
Canvas.DrawRectangle(
new Position(-Left/2, availableSpace.Height-Bottom/2),
new Size(availableSpace.Width + Left/2 + Right/2, Bottom),
Color);
Canvas.DrawRectangle(
new Position(availableSpace.Width-Right/2, -Top/2),
new Size(Right, availableSpace.Height + Top/2 + Bottom/2),
Color);
if (HasEqualWidthOnAllSides())
{
Canvas.DrawStrokedRectangle(availableSpace, Color, Top);
}
else
{
DrawCustomRectangle(availableSpace);
}
}
public override string ToString()
private void DrawCustomRectangle(Size size)
{
return $"Border: Top({Top}) Right({Right}) Bottom({Bottom}) Left({Left}) Color({Color})";
Canvas.DrawFilledRectangle(
new Position(-Left / 2, -Top / 2),
new Size(size.Width + Left / 2 + Right / 2, Top),
Color);
Canvas.DrawFilledRectangle(
new Position(-Left / 2, -Top / 2),
new Size(Left, size.Height + Top / 2 + Bottom / 2),
Color);
Canvas.DrawFilledRectangle(
new Position(-Left / 2, size.Height - Bottom / 2),
new Size(size.Width + Left / 2 + Right / 2, Bottom),
Color);
Canvas.DrawFilledRectangle(
new Position(size.Width - Right / 2, -Top / 2),
new Size(Right, size.Height + Top / 2 + Bottom / 2),
Color);
}
private bool HasEqualWidthOnAllSides()
{
return IsClose(Top, Bottom) &&
IsClose(Left, Right) &&
IsClose(Top, Left);
}
private static bool IsClose(float x, float y)
{
return Math.Abs(x - y) < Size.Epsilon;
}
}
}
@@ -3,7 +3,7 @@ using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class MinimalBox : ContainerElement
internal class Box : ContainerElement
{
internal override void Draw(Size availableSpace)
{
-123
View File
@@ -1,123 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Drawing;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class ColumnItem : Container
{
public bool IsRendered { get; set; }
}
internal class ColumnItemRenderingCommand
{
public ColumnItem ColumnItem { get; set; }
public SpacePlan Measurement { get; set; }
public Size Size { get; set; }
public Position Offset { get; set; }
}
internal class Column : Element, ICacheable, IStateResettable
{
internal List<ColumnItem> Items { get; } = new();
internal float Spacing { get; set; }
public void ResetState()
{
Items.ForEach(x => x.IsRendered = false);
}
internal override IEnumerable<Element?> GetChildren()
{
return Items;
}
internal override void CreateProxy(Func<Element?, Element?> create)
{
Items.ForEach(x => x.Child = create(x.Child));
}
internal override SpacePlan Measure(Size availableSpace)
{
if (!Items.Any())
return SpacePlan.FullRender(Size.Zero);
var renderingCommands = PlanLayout(availableSpace);
if (!renderingCommands.Any())
return SpacePlan.Wrap();
var width = renderingCommands.Max(x => x.Size.Width);
var height = renderingCommands.Last().Offset.Y + renderingCommands.Last().Size.Height;
var size = new Size(width, height);
if (width > availableSpace.Width + Size.Epsilon || height > availableSpace.Height + Size.Epsilon)
return SpacePlan.Wrap();
var totalRenderedItems = Items.Count(x => x.IsRendered) + renderingCommands.Count(x => x.Measurement.Type == SpacePlanType.FullRender);
var willBeFullyRendered = totalRenderedItems == Items.Count;
return willBeFullyRendered
? SpacePlan.FullRender(size)
: SpacePlan.PartialRender(size);
}
internal override void Draw(Size availableSpace)
{
var renderingCommands = PlanLayout(availableSpace);
foreach (var command in renderingCommands)
{
if (command.Measurement.Type == SpacePlanType.FullRender)
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());
}
if (Items.All(x => x.IsRendered))
ResetState();
}
private ICollection<ColumnItemRenderingCommand> PlanLayout(Size availableSpace)
{
var topOffset = 0f;
var commands = new List<ColumnItemRenderingCommand>();
foreach (var item in Items)
{
if (item.IsRendered)
continue;
var itemSpace = new Size(availableSpace.Width, availableSpace.Height - topOffset);
var measurement = item.Measure(itemSpace);
if (measurement.Type == SpacePlanType.Wrap)
break;
commands.Add(new ColumnItemRenderingCommand
{
ColumnItem = item,
Size = measurement,
Measurement = measurement,
Offset = new Position(0, topOffset)
});
if (measurement.Type == SpacePlanType.PartialRender)
break;
topOffset += measurement.Height + Spacing;
}
var targetWidth = commands.Select(x => x.Size.Width).DefaultIfEmpty(0).Max();
commands.ForEach(x => x.Size = new Size(targetWidth, x.Size.Height));
return commands;
}
}
}
+1 -1
View File
@@ -27,7 +27,7 @@ namespace QuestPDF.Elements
.Layer()
.ShowIf(!string.IsNullOrWhiteSpace(Text))
.AlignCenter()
.MinimalBox()
.Box()
.Background(Colors.White)
.Padding(2)
.Text(Text, TextStyle.Default.Color(Color).FontType(Fonts.Consolas).Size(8));
+64 -77
View File
@@ -1,112 +1,99 @@
using System;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Drawing;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class DecorationItemRenderingCommand
internal enum DecorationType
{
public Element Element { get; set; }
public SpacePlan Measurement { get; set; }
public Position Offset { get; set; }
Prepend,
Append
}
internal class Decoration : Element, ICacheable
internal class BinaryDecoration : Element, ICacheable
{
internal Element Before { get; set; } = new Empty();
internal Element Content { get; set; } = new Empty();
internal Element After { get; set; } = new Empty();
public Element DecorationElement { get; set; } = Empty.Instance;
public Element ContentElement { get; set; } = Empty.Instance;
public DecorationType Type { get; set; }
internal override IEnumerable<Element?> GetChildren()
internal override void HandleVisitor(Action<Element?> visit)
{
yield return Before;
yield return Content;
yield return After;
DecorationElement?.HandleVisitor(visit);
ContentElement?.HandleVisitor(visit);
base.HandleVisitor(visit);
}
internal override void CreateProxy(Func<Element?, Element?> create)
internal override void CreateProxy(Func<Element, Element> create)
{
Before = create(Before);
Content = create(Content);
After = create(After);
DecorationElement = create(DecorationElement);
ContentElement = create(ContentElement);
}
internal override SpacePlan Measure(Size availableSpace)
{
var renderingCommands = PlanLayout(availableSpace).ToList();
if (renderingCommands.Any(x => x.Measurement.Type == SpacePlanType.Wrap))
var decorationMeasure = DecorationElement.Measure(availableSpace);
if (decorationMeasure.Type == SpacePlanType.Wrap || decorationMeasure.Type == SpacePlanType.PartialRender)
return SpacePlan.Wrap();
var width = renderingCommands.Max(x => x.Measurement.Width);
var height = renderingCommands.Sum(x => x.Measurement.Height);
var size = new Size(width, height);
var decorationSize = decorationMeasure;
var contentMeasure = ContentElement.Measure(new Size(availableSpace.Width, availableSpace.Height - decorationSize.Height));
if (width > availableSpace.Width + Size.Epsilon || height > availableSpace.Height + Size.Epsilon)
if (contentMeasure.Type == SpacePlanType.Wrap)
return SpacePlan.Wrap();
var willBeFullyRendered = renderingCommands.All(x => x.Measurement.Type == SpacePlanType.FullRender);
return willBeFullyRendered
? SpacePlan.FullRender(size)
: SpacePlan.PartialRender(size);
var contentSize = contentMeasure;
var resultSize = new Size(availableSpace.Width, decorationSize.Height + contentSize.Height);
if (contentSize.Type == SpacePlanType.PartialRender)
return SpacePlan.PartialRender(resultSize);
if (contentSize.Type == SpacePlanType.FullRender)
return SpacePlan.FullRender(resultSize);
throw new NotSupportedException();
}
internal override void Draw(Size availableSpace)
{
var renderingCommands = PlanLayout(availableSpace).ToList();
var width = renderingCommands.Max(x => x.Measurement.Width);
foreach (var command in renderingCommands)
{
var elementSize = new Size(width, command.Measurement.Height);
Canvas.Translate(command.Offset);
command.Element.Draw(elementSize);
Canvas.Translate(command.Offset.Reverse());
}
var decorationSize = DecorationElement.Measure(availableSpace);
var contentSize = new Size(availableSpace.Width, availableSpace.Height - decorationSize.Height);
var translateHeight = Type == DecorationType.Prepend ? decorationSize.Height : contentSize.Height;
Action drawDecoration = () => DecorationElement?.Draw(new Size(availableSpace.Width, decorationSize.Height));
Action drawContent = () => ContentElement?.Draw(new Size (availableSpace.Width, contentSize.Height));
var first = Type == DecorationType.Prepend ? drawDecoration : drawContent;
var second = Type == DecorationType.Prepend ? drawContent : drawDecoration;
first();
Canvas.Translate(new Position(0, translateHeight));
second();
Canvas.Translate(new Position(0, -translateHeight));
}
}
internal class Decoration : IComponent
{
public Element Header { get; set; } = Empty.Instance;
public Element Content { get; set; } = Empty.Instance;
public Element Footer { get; set; } = Empty.Instance;
private IEnumerable<DecorationItemRenderingCommand> PlanLayout(Size availableSpace)
public void Compose(IContainer container)
{
SpacePlan GetDecorationMeasurement(Element element)
container.Element(new BinaryDecoration
{
var measurement = element.Measure(availableSpace);
return measurement.Type == SpacePlanType.FullRender
? measurement
: SpacePlan.Wrap();
}
var beforeMeasurement = GetDecorationMeasurement(Before);
var afterMeasurement = GetDecorationMeasurement(After);
var contentSpace = new Size(availableSpace.Width, availableSpace.Height - beforeMeasurement.Height - afterMeasurement.Height);
var contentMeasurement = Content.Measure(contentSpace);
yield return new DecorationItemRenderingCommand
{
Element = Before,
Measurement = beforeMeasurement,
Offset = Position.Zero
};
yield return new DecorationItemRenderingCommand
{
Element = Content,
Measurement = contentMeasurement,
Offset = new Position(0, beforeMeasurement.Height)
};
yield return new DecorationItemRenderingCommand
{
Element = After,
Measurement = afterMeasurement,
Offset = new Position(0, beforeMeasurement.Height + contentMeasurement.Height)
};
Type = DecorationType.Prepend,
DecorationElement = Header,
ContentElement = new BinaryDecoration
{
Type = DecorationType.Append,
ContentElement = Content,
DecorationElement = Footer
}
});
}
}
}
-10
View File
@@ -1,10 +0,0 @@
using QuestPDF.Drawing;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class DefaultTextStyle : ContainerElement
{
public TextStyle TextStyle { get; set; } = TextStyle.Default;
}
}
@@ -3,7 +3,7 @@ using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class Hyperlink : ContainerElement
internal class ExternalLink : ContainerElement
{
public string Url { get; set; } = "https://www.questpdf.com";
@@ -14,7 +14,7 @@ namespace QuestPDF.Elements
if (targetSize.Type == SpacePlanType.Wrap)
return;
Canvas.DrawHyperlink(Url, targetSize);
Canvas.DrawExternalLink(Url, targetSize);
base.Draw(availableSpace);
}
}
+6 -6
View File
@@ -27,12 +27,12 @@ namespace QuestPDF.Elements
{
ChildrenQueue = new Queue<GridElement>(Children);
container.Column(column =>
container.Stack(stack =>
{
column.Spacing(VerticalSpacing);
stack.Spacing(VerticalSpacing);
while (ChildrenQueue.Any())
column.Item().Row(BuildRow);
stack.Item().Row(BuildRow);
});
}
@@ -65,12 +65,12 @@ namespace QuestPDF.Elements
emptySpace /= 2;
if (hasEmptySpace && Alignment != HorizontalAlignment.Left)
row.RelativeItem(emptySpace);
row.RelativeColumn(emptySpace);
elements.ForEach(x => row.RelativeItem(x.Columns).Element(x.Child));
elements.ForEach(x => row.RelativeColumn(x.Columns).Element(x.Child));
if (hasEmptySpace && Alignment != HorizontalAlignment.Right)
row.RelativeItem(emptySpace);
row.RelativeColumn(emptySpace);
}
}
}
+49 -35
View File
@@ -8,7 +8,15 @@ namespace QuestPDF.Elements
{
internal class InlinedElement : Container
{
public SpacePlan? MeasureCache { get; set; }
internal override SpacePlan Measure(Size availableSpace)
{
// TODO: once element caching proxy is introduces, this can be removed
MeasureCache ??= Child.Measure(Size.Max);
return MeasureCache.Value;
}
}
internal enum InlinedAlignment
@@ -36,11 +44,12 @@ namespace QuestPDF.Elements
ChildrenQueue = new Queue<InlinedElement>(Elements);
}
internal override IEnumerable<Element?> GetChildren()
internal override void HandleVisitor(Action<Element?> visit)
{
return Elements;
Elements.ForEach(x => x.HandleVisitor(visit));
base.HandleVisitor(visit);
}
internal override SpacePlan Measure(Size availableSpace)
{
if (!ChildrenQueue.Any())
@@ -104,11 +113,8 @@ namespace QuestPDF.Elements
foreach (var element in elements)
{
var size = (Size)element.Measure(Size.Max);
var size = element.Measure(Size.Max);
var baselineOffset = BaselineOffset(size, lineSize.Height);
if (size.Height == 0)
size = new Size(size.Width, lineSize.Height);
Canvas.Translate(new Position(0, baselineOffset));
element.Draw(size);
@@ -127,39 +133,48 @@ namespace QuestPDF.Elements
if (elements.Count == 1)
return 0;
return ElementsAlignment switch
{
InlinedAlignment.Justify => difference / (elements.Count - 1),
InlinedAlignment.SpaceAround => difference / (elements.Count + 1),
_ => HorizontalSpacing
};
if (ElementsAlignment == InlinedAlignment.Justify)
return difference / (elements.Count - 1);
if (ElementsAlignment == InlinedAlignment.SpaceAround)
return difference / (elements.Count + 1);
return HorizontalSpacing;
}
float AlignOffset()
{
var difference = availableSpace.Width - lineSize.Width - (elements.Count - 1) * HorizontalSpacing;
if (ElementsAlignment == InlinedAlignment.Left)
return 0;
if (ElementsAlignment == InlinedAlignment.Justify)
return 0;
if (ElementsAlignment == InlinedAlignment.SpaceAround)
return elementOffset;
return ElementsAlignment switch
{
InlinedAlignment.Left => 0,
InlinedAlignment.Justify => 0,
InlinedAlignment.SpaceAround => elementOffset,
InlinedAlignment.Center => difference / 2,
InlinedAlignment.Right => difference,
_ => 0
};
var difference = availableSpace.Width - lineSize.Width - (elements.Count - 1) * HorizontalSpacing;
if (ElementsAlignment == InlinedAlignment.Center)
return difference / 2;
if (ElementsAlignment == InlinedAlignment.Right)
return difference;
return 0;
}
float BaselineOffset(Size elementSize, float lineHeight)
{
var difference = lineHeight - elementSize.Height;
if (BaselineAlignment == VerticalAlignment.Top)
return 0;
return BaselineAlignment switch
{
VerticalAlignment.Top => 0,
VerticalAlignment.Middle => difference / 2,
_ => difference
};
var difference = lineHeight - elementSize.Height;
if (BaselineAlignment == VerticalAlignment.Middle)
return difference / 2;
return difference;
}
}
}
@@ -236,12 +251,11 @@ namespace QuestPDF.Elements
float GetInitialAlignmentOffset()
{
// this method makes sure that the spacing between elements is no lesser than configured
if (ElementsAlignment == InlinedAlignment.SpaceAround)
return HorizontalSpacing * 2;
return ElementsAlignment switch
{
InlinedAlignment.SpaceAround => HorizontalSpacing * 2,
_ => 0
};
return 0;
}
}
}
@@ -3,9 +3,9 @@ using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class SectionLink : ContainerElement
internal class InternalLink : ContainerElement
{
public string SectionName { get; set; }
public string LocationName { get; set; }
internal override void Draw(Size availableSpace)
{
@@ -14,7 +14,7 @@ namespace QuestPDF.Elements
if (targetSize.Type == SpacePlanType.Wrap)
return;
Canvas.DrawSectionLink(SectionName, targetSize);
Canvas.DrawLocationLink(LocationName, targetSize);
base.Draw(availableSpace);
}
}
@@ -2,7 +2,7 @@
namespace QuestPDF.Elements
{
internal class Section : ContainerElement, IStateResettable
internal class InternalLocation : ContainerElement, IStateResettable
{
public string LocationName { get; set; }
private bool IsRendered { get; set; }
@@ -16,11 +16,12 @@ namespace QuestPDF.Elements
{
if (!IsRendered)
{
Canvas.DrawSection(LocationName);
PageContext.SetLocationPage(LocationName);
Canvas.DrawLocation(LocationName);
IsRendered = true;
}
PageContext.SetSectionPage(LocationName);
base.Draw(availableSpace);
}
}
+4 -3
View File
@@ -15,11 +15,12 @@ namespace QuestPDF.Elements
{
public List<Layer> Children { get; set; } = new List<Layer>();
internal override IEnumerable<Element?> GetChildren()
internal override void HandleVisitor(Action<Element?> visit)
{
return Children;
Children.ForEach(x => x.HandleVisitor(visit));
base.HandleVisitor(visit);
}
internal override SpacePlan Measure(Size availableSpace)
{
return Children
-46
View File
@@ -1,46 +0,0 @@
using QuestPDF.Drawing;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
public interface ILine
{
}
internal enum LineType
{
Vertical,
Horizontal
}
internal class Line : Element, ILine, ICacheable
{
public LineType Type { get; set; } = LineType.Vertical;
public string Color { get; set; } = Colors.Black;
public float Size { get; set; } = 1;
internal override SpacePlan Measure(Size availableSpace)
{
return Type switch
{
LineType.Vertical when availableSpace.Width >= Size => SpacePlan.FullRender(Size, 0),
LineType.Horizontal when availableSpace.Height >= Size => SpacePlan.FullRender(0, Size),
_ => SpacePlan.Wrap()
};
}
internal override void Draw(Size availableSpace)
{
if (Type == LineType.Vertical)
{
Canvas.DrawRectangle(new Position(-Size/2, 0), new Size(Size, availableSpace.Height), Color);
}
else if (Type == LineType.Horizontal)
{
Canvas.DrawRectangle(new Position(0, -Size/2), new Size(availableSpace.Width, Size), Color);
}
}
}
}
+4 -4
View File
@@ -40,12 +40,10 @@ namespace QuestPDF.Elements
.PaddingTop(MarginTop)
.PaddingBottom(MarginBottom)
.DefaultTextStyle(DefaultTextStyle)
.Decoration(decoration =>
{
decoration
.Before()
.Header()
.DebugPointer("Page header")
.Element(Header);
@@ -57,11 +55,13 @@ namespace QuestPDF.Elements
.Element(Content);
decoration
.After()
.Footer()
.DebugPointer("Page footer")
.Element(Footer);
});
(container as Element).ApplyDefaultTextStyle(DefaultTextStyle);
bool IsClose(float x, float y)
{
return Math.Abs(x - y) < Size.Epsilon;
+145 -132
View File
@@ -1,5 +1,4 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Drawing;
@@ -7,162 +6,176 @@ using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal enum RowItemType
internal abstract class RowElement : Constrained
{
Auto,
Constant,
Relative
public float Size { get; protected set; }
public void SetWidth(float width)
{
MinWidth = width;
MaxWidth = width;
}
}
internal class RowItem : Container
internal class ConstantRowElement : RowElement
{
public bool IsRendered { get; set; }
public float Width { get; set; }
public RowItemType Type { get; set; }
public float Size { get; set; }
}
internal class RowItemRenderingCommand
{
public RowItem RowItem { get; set; }
public SpacePlan Measurement { get; set; }
public Size Size { get; set; }
public Position Offset { get; set; }
public ConstantRowElement(float size)
{
Size = size;
SetWidth(size);
}
}
internal class Row : Element, ICacheable, IStateResettable
internal class RelativeRowElement : RowElement
{
internal List<RowItem> Items { get; } = new();
internal float Spacing { get; set; }
public RelativeRowElement(float size)
{
Size = size;
}
}
internal class BinaryRow : Element, ICacheable
{
internal Element Left { get; set; }
internal Element Right { get; set; }
public void ResetState()
internal override void HandleVisitor(Action<Element?> visit)
{
Items.ForEach(x => x.IsRendered = false);
Left.HandleVisitor(visit);
Right.HandleVisitor(visit);
base.HandleVisitor(visit);
}
internal override IEnumerable<Element?> GetChildren()
{
return Items;
}
internal override void CreateProxy(Func<Element?, Element?> create)
{
Items.ForEach(x => x.Child = create(x.Child));
Left = create(Left);
Right = create(Right);
}
internal override SpacePlan Measure(Size availableSpace)
{
if (!Items.Any())
return SpacePlan.FullRender(Size.Zero);
var leftMeasurement = Left.Measure(new Size(availableSpace.Width, availableSpace.Height));
UpdateItemsWidth(availableSpace.Width);
var renderingCommands = PlanLayout(availableSpace);
if (renderingCommands.Any(x => !x.RowItem.IsRendered && x.Measurement.Type == SpacePlanType.Wrap))
return SpacePlan.Wrap();
var width = renderingCommands.Last().Offset.X + renderingCommands.Last().Size.Width;
var height = renderingCommands.Max(x => x.Size.Height);
var size = new Size(width, height);
if (width > availableSpace.Width + Size.Epsilon || height > availableSpace.Height + Size.Epsilon)
if (leftMeasurement.Type == SpacePlanType.Wrap)
return SpacePlan.Wrap();
if (renderingCommands.Any(x => !x.RowItem.IsRendered && x.Measurement.Type == SpacePlanType.PartialRender))
return SpacePlan.PartialRender(size);
var rightMeasurement = Right.Measure(new Size(availableSpace.Width - leftMeasurement.Width, availableSpace.Height));
return SpacePlan.FullRender(size);
if (rightMeasurement.Type == SpacePlanType.Wrap)
return SpacePlan.Wrap();
var totalWidth = leftMeasurement.Width + rightMeasurement.Width;
var totalHeight = Math.Max(leftMeasurement.Height, rightMeasurement.Height);
var targetSize = new Size(totalWidth, totalHeight);
if (leftMeasurement.Type == SpacePlanType.PartialRender || rightMeasurement.Type == SpacePlanType.PartialRender)
return SpacePlan.PartialRender(targetSize);
return SpacePlan.FullRender(targetSize);
}
internal override void Draw(Size availableSpace)
{
if (!Items.Any())
return;
UpdateItemsWidth(availableSpace.Width);
var renderingCommands = PlanLayout(availableSpace);
foreach (var command in renderingCommands)
{
if (command.Measurement.Type == SpacePlanType.FullRender)
command.RowItem.IsRendered = true;
if (command.Measurement.Type == SpacePlanType.Wrap)
continue;
Canvas.Translate(command.Offset);
command.RowItem.Draw(command.Size);
Canvas.Translate(command.Offset.Reverse());
}
var leftMeasurement = Left.Measure(new Size(availableSpace.Width, availableSpace.Height));
var leftWidth = leftMeasurement.Width;
if (Items.All(x => x.IsRendered))
ResetState();
}
private void UpdateItemsWidth(float availableWidth)
{
HandleItemsWithAutoWidth();
Left.Draw(new Size(leftWidth, availableSpace.Height));
var constantWidth = Items.Where(x => x.Type == RowItemType.Constant).Sum(x => x.Size);
var relativeWidth = Items.Where(x => x.Type == RowItemType.Relative).Sum(x => x.Size);
var spacingWidth = (Items.Count - 1) * Spacing;
foreach (var item in Items.Where(x => x.Type == RowItemType.Constant))
item.Width = item.Size;
if (relativeWidth <= 0)
return;
var widthPerRelativeUnit = (availableWidth - constantWidth - spacingWidth) / relativeWidth;
foreach (var item in Items.Where(x => x.Type == RowItemType.Relative))
item.Width = item.Size * widthPerRelativeUnit;
}
private void HandleItemsWithAutoWidth()
{
foreach (var rowItem in Items.Where(x => x.Type == RowItemType.Auto))
{
rowItem.Size = rowItem.Measure(Size.Max).Width;
rowItem.Type = RowItemType.Constant;
}
}
private ICollection<RowItemRenderingCommand> PlanLayout(Size availableSpace)
{
var leftOffset = 0f;
var renderingCommands = new List<RowItemRenderingCommand>();
foreach (var item in Items)
{
var itemSpace = new Size(item.Width, availableSpace.Height);
var command = new RowItemRenderingCommand
{
RowItem = item,
Size = itemSpace,
Measurement = item.Measure(itemSpace),
Offset = new Position(leftOffset, 0)
};
renderingCommands.Add(command);
leftOffset += item.Width + Spacing;
}
var rowHeight = renderingCommands
.Where(x => !x.RowItem.IsRendered)
.Select(x => x.Measurement.Height)
.DefaultIfEmpty(0)
.Max();
foreach (var command in renderingCommands)
{
command.Size = new Size(command.Size.Width, rowHeight);
command.Measurement = command.RowItem.Measure(command.Size);
}
return renderingCommands;
Canvas.Translate(new Position(leftWidth, 0));
Right.Draw(new Size(availableSpace.Width - leftWidth, availableSpace.Height));
Canvas.Translate(new Position(-leftWidth, 0));
}
}
internal class Row : Element
{
public float Spacing { get; set; } = 0;
public ICollection<RowElement> Children { get; internal set; } = new List<RowElement>();
private Element? RootElement { get; set; }
internal override void HandleVisitor(Action<Element?> visit)
{
if (RootElement == null)
ComposeTree();
RootElement.HandleVisitor(visit);
base.HandleVisitor(visit);
}
internal override SpacePlan Measure(Size availableSpace)
{
UpdateElementsWidth(availableSpace.Width);
return RootElement.Measure(availableSpace);
}
internal override void Draw(Size availableSpace)
{
UpdateElementsWidth(availableSpace.Width);
RootElement.Draw(availableSpace);
}
#region structure
private void ComposeTree()
{
Children = AddSpacing(Children, Spacing);
var elements = Children.Cast<Element>().ToArray();
RootElement = BuildTree(elements);
}
private void UpdateElementsWidth(float availableWidth)
{
var constantWidth = Children
.Where(x => x is ConstantRowElement)
.Cast<ConstantRowElement>()
.Sum(x => x.Size);
var relativeWidth = Children
.Where(x => x is RelativeRowElement)
.Cast<RelativeRowElement>()
.Sum(x => x.Size);
var widthPerRelativeUnit = (availableWidth - constantWidth) / relativeWidth;
Children
.Where(x => x is RelativeRowElement)
.Cast<RelativeRowElement>()
.ToList()
.ForEach(x => x.SetWidth(x.Size * widthPerRelativeUnit));
}
private static ICollection<RowElement> AddSpacing(ICollection<RowElement> elements, float spacing)
{
if (spacing < Size.Epsilon)
return elements;
return elements
.SelectMany(x => new[] { new ConstantRowElement(spacing), x })
.Skip(1)
.ToList();
}
private static Element BuildTree(Span<Element> elements)
{
if (elements.IsEmpty)
return Empty.Instance;
if (elements.Length == 1)
return elements[0];
var half = elements.Length / 2;
return new BinaryRow
{
Left = BuildTree(elements.Slice(0, half)),
Right = BuildTree(elements.Slice(half))
};
}
#endregion
}
}
-78
View File
@@ -1,78 +0,0 @@
using System;
using System.Linq;
using QuestPDF.Drawing;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class ScaleToFit : ContainerElement
{
internal override SpacePlan Measure(Size availableSpace)
{
if (Child == null)
return SpacePlan.FullRender(Size.Zero);
var perfectScale = FindPerfectScale(Child, availableSpace);
if (perfectScale == null)
return SpacePlan.Wrap();
var targetSpace = ScaleSize(availableSpace, perfectScale.Value);
return SpacePlan.FullRender(targetSpace);
}
internal override void Draw(Size availableSpace)
{
var perfectScale = FindPerfectScale(Child, availableSpace);
if (!perfectScale.HasValue)
return;
var targetScale = perfectScale.Value;
var targetSpace = ScaleSize(availableSpace, 1 / targetScale);
Canvas.Scale(targetScale, targetScale);
Child?.Draw(targetSpace);
Canvas.Scale(1 / targetScale, 1 / targetScale);
}
private static Size ScaleSize(Size size, float factor)
{
return new Size(size.Width * factor, size.Height * factor);
}
private static float? FindPerfectScale(Element child, Size availableSpace)
{
if (ChildFits(1))
return 1;
var maxScale = 1f;
var minScale = Size.Epsilon;
var lastWorkingScale = (float?)null;
foreach (var _ in Enumerable.Range(0, 8))
{
var halfScale = (maxScale + minScale) / 2;
if (ChildFits(halfScale))
{
minScale = halfScale;
lastWorkingScale = halfScale;
}
else
{
maxScale = halfScale;
}
}
return lastWorkingScale;
bool ChildFits(float scale)
{
var scaledSpace = ScaleSize(availableSpace, 1 / scale);
return child.Measure(scaledSpace).Type == SpacePlanType.FullRender;
}
}
}
}
+145
View File
@@ -0,0 +1,145 @@
using System;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Drawing;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
using IComponent = QuestPDF.Infrastructure.IComponent;
using IContainer = QuestPDF.Infrastructure.IContainer;
namespace QuestPDF.Elements
{
internal class BinaryStack : Element, IStateResettable, ICacheable
{
internal Element First { get; set; } = Empty.Instance;
internal Element Second { get; set; } = Empty.Instance;
internal bool IsFirstRendered { get; set; } = false;
internal override void HandleVisitor(Action<Element?> visit)
{
First?.HandleVisitor(visit);
Second?.HandleVisitor(visit);
base.HandleVisitor(visit);
}
public void ResetState()
{
IsFirstRendered = false;
}
internal override void CreateProxy(Func<Element?, Element?> create)
{
First = create(First);
Second = create(Second);
}
internal override SpacePlan Measure(Size availableSpace)
{
var firstElement = IsFirstRendered ? Empty.Instance : First;
var firstSize = firstElement.Measure(availableSpace);
if (firstSize.Type == SpacePlanType.Wrap)
return SpacePlan.Wrap();
if (firstSize.Type == SpacePlanType.PartialRender)
return firstSize;
var spaceForSecond = new Size(availableSpace.Width, availableSpace.Height - firstSize.Height);
var secondSize = Second.Measure(spaceForSecond);
if (secondSize.Type == SpacePlanType.Wrap)
return SpacePlan.PartialRender(firstSize);
var totalWidth = Math.Max(firstSize.Width, secondSize.Width);
var totalHeight = firstSize.Height + secondSize.Height;
var targetSize = new Size(totalWidth, totalHeight);
if (secondSize.Type == SpacePlanType.PartialRender)
return SpacePlan.PartialRender(targetSize);
return SpacePlan.FullRender(targetSize);
}
internal override void Draw(Size availableSpace)
{
var firstElement = IsFirstRendered ? Empty.Instance : First;
var firstMeasurement = firstElement.Measure(availableSpace);
if (firstMeasurement.Type == SpacePlanType.FullRender)
IsFirstRendered = true;
var firstSize = firstMeasurement;
if (firstSize.Type != SpacePlanType.Wrap)
firstElement.Draw(new Size(availableSpace.Width, firstSize.Height));
if (firstMeasurement.Type == SpacePlanType.Wrap || firstMeasurement.Type == SpacePlanType.PartialRender)
return;
var firstHeight = firstSize.Height;
var spaceForSecond = new Size(availableSpace.Width, availableSpace.Height - firstHeight);
var secondMeasurement = Second.Measure(spaceForSecond);
if (secondMeasurement.Type == SpacePlanType.Wrap)
return;
Canvas.Translate(new Position(0, firstHeight));
Second.Draw(new Size(availableSpace.Width, secondMeasurement.Height));
Canvas.Translate(new Position(0, -firstHeight));
if (secondMeasurement.Type == SpacePlanType.FullRender)
IsFirstRendered = false;
}
}
internal class Stack : IComponent
{
public ICollection<Element> Children { get; } = new List<Element>();
public float Spacing { get; set; } = 0;
public void Compose(IContainer container)
{
var elements = AddSpacing(Spacing, Children);
container
.PaddingBottom(-Spacing)
.Element(BuildTree(elements.ToArray()));
}
static ICollection<Element> AddSpacing(float spacing, ICollection<Element> elements)
{
if (spacing < Size.Epsilon)
return elements;
return elements
.Where(x => !(x is Empty))
.Select(x => new Padding
{
Bottom = spacing,
Child = x
})
.Cast<Element>()
.ToList();
}
static Element BuildTree(Span<Element> elements)
{
if (elements.IsEmpty)
return Empty.Instance;
if (elements.Length == 1)
return elements[0];
var half = elements.Length / 2;
return new BinaryStack
{
First = BuildTree(elements.Slice(0, half)),
Second = BuildTree(elements.Slice(half))
};
}
}
}
-25
View File
@@ -1,25 +0,0 @@
using System;
using QuestPDF.Drawing;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class StopPaging : ContainerElement
{
internal override SpacePlan Measure(Size availableSpace)
{
if (Child == null)
return SpacePlan.FullRender(Size.Zero);
var measurement = Child.Measure(availableSpace);
return measurement.Type switch
{
SpacePlanType.Wrap => SpacePlan.FullRender(Size.Zero),
SpacePlanType.PartialRender => SpacePlan.FullRender(measurement),
SpacePlanType.FullRender => measurement,
_ => throw new ArgumentOutOfRangeException()
};
}
}
}
@@ -1,33 +0,0 @@
using System.Collections.Generic;
using System.Linq;
namespace QuestPDF.Elements.Table
{
/// <summary>
/// This dictionary allows to access key that does not exist.
/// Instead of throwing an exception, it returns a default value.
/// </summary>
internal class DynamicDictionary<TKey, TValue>
{
private TValue Default { get; }
private IDictionary<TKey, TValue> Dictionary { get; } = new Dictionary<TKey, TValue>();
public DynamicDictionary()
{
}
public DynamicDictionary(TValue defaultValue)
{
Default = defaultValue;
}
public TValue this[TKey key]
{
get => Dictionary.TryGetValue(key, out var value) ? value : Default;
set => Dictionary[key] = value;
}
public List<KeyValuePair<TKey, TValue>> Items => Dictionary.ToList();
}
}
@@ -1,9 +0,0 @@
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Table
{
public interface ITableCellContainer : IContainer
{
}
}
-259
View File
@@ -1,259 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using QuestPDF.Drawing;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Table
{
internal class Table : Element, IStateResettable
{
public List<TableColumnDefinition> Columns { get; set; } = new List<TableColumnDefinition>();
public List<TableCell> Cells { get; set; } = new List<TableCell>();
public bool ExtendLastCellsToTableBottom { get; set; }
private int StartingRowsCount { get; set; }
private int RowsCount { get; set; }
private int CurrentRow { 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();
base.Initialize(pageContext, canvas);
}
internal override IEnumerable<Element?> GetChildren()
{
return Cells;
}
public void ResetState()
{
Cells.ForEach(x => x.IsRendered = false);
CurrentRow = 1;
}
internal override SpacePlan Measure(Size availableSpace)
{
if (!Cells.Any())
return SpacePlan.FullRender(Size.Zero);
UpdateColumnsWidth(availableSpace.Width);
var renderingCommands = PlanLayout(availableSpace);
if (!renderingCommands.Any())
return SpacePlan.Wrap();
var width = Columns.Sum(x => x.Width);
var height = renderingCommands.Max(x => x.Offset.Y + x.Size.Height);
var tableSize = new Size(width, height);
if (tableSize.Width > availableSpace.Width + Size.Epsilon)
return SpacePlan.Wrap();
return FindLastRenderedRow(renderingCommands) == StartingRowsCount
? SpacePlan.FullRender(tableSize)
: SpacePlan.PartialRender(tableSize);
}
internal override void Draw(Size availableSpace)
{
UpdateColumnsWidth(availableSpace.Width);
var renderingCommands = PlanLayout(availableSpace);
foreach (var command in renderingCommands)
{
if (command.Measurement.Type == SpacePlanType.FullRender)
command.Cell.IsRendered = true;
if (command.Measurement.Type == SpacePlanType.Wrap)
continue;
Canvas.Translate(command.Offset);
command.Cell.Draw(command.Size);
Canvas.Translate(command.Offset.Reverse());
}
CurrentRow = FindLastRenderedRow(renderingCommands) + 1;
var isFullRender = FindLastRenderedRow(renderingCommands) == StartingRowsCount;
if (isFullRender)
ResetState();
}
private int FindLastRenderedRow(ICollection<TableCellRenderingCommand> commands)
{
return commands
.GroupBy(x => x.Cell.Row)
.Where(x => x.All(y => y.Cell.IsRendered || y.Measurement.Type == SpacePlanType.FullRender))
.Select(x => x.Key)
.DefaultIfEmpty(0)
.Max();
}
private void UpdateColumnsWidth(float availableWidth)
{
var constantWidth = Columns.Sum(x => x.ConstantSize);
var relativeWidth = Columns.Sum(x => x.RelativeSize);
var widthPerRelativeUnit = (relativeWidth > 0) ? (availableWidth - constantWidth) / relativeWidth : 0;
foreach (var column in Columns)
{
column.Width = column.ConstantSize + column.RelativeSize * widthPerRelativeUnit;
}
}
private ICollection<TableCellRenderingCommand> PlanLayout(Size availableSpace)
{
var columnOffsets = GetColumnLeftOffsets(Columns);
var commands = GetRenderingCommands();
if (!commands.Any())
return commands;
var tableHeight = commands.Max(cell => cell.Offset.Y + cell.Size.Height);
if (ExtendLastCellsToTableBottom)
AdjustLastCellSizes(tableHeight, commands);
return commands;
static float[] GetColumnLeftOffsets(IList<TableColumnDefinition> columns)
{
var cellOffsets = new float[columns.Count + 1];
cellOffsets[0] = 0;
foreach (var column in Enumerable.Range(1, cellOffsets.Length - 1))
cellOffsets[column] = columns[column - 1].Width + cellOffsets[column - 1];
return cellOffsets;
}
ICollection<TableCellRenderingCommand> GetRenderingCommands()
{
var rowBottomOffsets = new DynamicDictionary<int, float>();
var commands = new List<TableCellRenderingCommand>();
var cellsToTry = Cells.Where(x => x.Row + x.RowSpan - 1 >= CurrentRow);
var currentRow = CurrentRow;
var maxRenderingRow = RowsCount;
foreach (var cell in cellsToTry)
{
// update position of previous row
if (cell.Row > currentRow)
{
rowBottomOffsets[currentRow] = Math.Max(rowBottomOffsets[currentRow], rowBottomOffsets[currentRow - 1]);
if (rowBottomOffsets[currentRow - 1] > availableSpace.Height + Size.Epsilon)
break;
foreach (var row in Enumerable.Range(cell.Row, cell.Row - currentRow))
rowBottomOffsets[row] = Math.Max(rowBottomOffsets[row - 1], rowBottomOffsets[row]);
currentRow = cell.Row;
}
// cell visibility optimizations
if (cell.Row > maxRenderingRow)
break;
// calculate cell position / size
var topOffset = rowBottomOffsets[cell.Row - 1];
var availableWidth = GetCellWidth(cell);
var availableHeight = availableSpace.Height - topOffset;
var availableCellSize = new Size(availableWidth, availableHeight);
var cellSize = cell.Measure(availableCellSize);
// corner case: if cell within the row is not fully rendered, do not attempt to render next row
if (cellSize.Type == SpacePlanType.PartialRender)
{
maxRenderingRow = Math.Min(maxRenderingRow, cell.Row + cell.RowSpan - 1);
}
// corner case: if cell within the row want to wrap to the next page, do not attempt to render this row
if (cellSize.Type == SpacePlanType.Wrap)
{
maxRenderingRow = Math.Min(maxRenderingRow, cell.Row - 1);
continue;
}
// update position of the last row that cell occupies
var bottomRow = cell.Row + cell.RowSpan - 1;
rowBottomOffsets[bottomRow] = Math.Max(rowBottomOffsets[bottomRow], topOffset + cellSize.Height);
// accept cell to be rendered
commands.Add(new TableCellRenderingCommand()
{
Cell = cell,
Measurement = cellSize,
Size = new Size(availableWidth, cellSize.Height),
Offset = new Position(columnOffsets[cell.Column - 1], topOffset)
});
}
if (!commands.Any())
return commands;
var maxRow = commands.Select(x => x.Cell).Max(x => x.Row + x.RowSpan);
foreach (var row in Enumerable.Range(CurrentRow, maxRow - CurrentRow))
rowBottomOffsets[row] = Math.Max(rowBottomOffsets[row - 1], rowBottomOffsets[row]);
AdjustCellSizes(commands, rowBottomOffsets);
// corner case: reject cell if other cells within the same row are rejected
return commands.Where(x => x.Cell.Row <= maxRenderingRow).ToList();
}
// corner sase: if two cells end up on the same row (a.Row + a.RowSpan = b.Row + b.RowSpan),
// bottom edges of their bounding boxes should be at the same level
static void AdjustCellSizes(ICollection<TableCellRenderingCommand> commands, DynamicDictionary<int, float> rowBottomOffsets)
{
foreach (var command in commands)
{
var lastRow = command.Cell.Row + command.Cell.RowSpan - 1;
var height = rowBottomOffsets[lastRow] - command.Offset.Y;
command.Size = new Size(command.Size.Width, height);
command.Offset = new Position(command.Offset.X, rowBottomOffsets[command.Cell.Row - 1]);
}
}
// corner sase: all cells, that are last ones in their respective columns, should take all remaining space
static void AdjustLastCellSizes(float tableHeight, ICollection<TableCellRenderingCommand> commands)
{
var columnsCount = commands.Select(x => x.Cell).Max(x => x.Column + x.ColumnSpan - 1);
foreach (var column in Enumerable.Range(1, columnsCount))
{
var lastCellInColumn = commands
.Where(x => x.Cell.Column <= column && column < x.Cell.Column + x.Cell.ColumnSpan)
.OrderByDescending(x => x.Cell.Row + x.Cell.RowSpan)
.FirstOrDefault();
if (lastCellInColumn == null)
continue;
lastCellInColumn.Size = new Size(lastCellInColumn.Size.Width, tableHeight - lastCellInColumn.Offset.Y);
}
}
float GetCellWidth(TableCell cell)
{
return columnOffsets[cell.Column + cell.ColumnSpan - 1] - columnOffsets[cell.Column - 1];
}
}
}
}
-13
View File
@@ -1,13 +0,0 @@
namespace QuestPDF.Elements.Table
{
internal class TableCell : Container, ITableCellContainer
{
public int Row { get; set; } = 0;
public int RowSpan { get; set; } = 1;
public int Column { get; set; } = 0;
public int ColumnSpan { get; set; } = 1;
public bool IsRendered { get; set; }
}
}
@@ -1,13 +0,0 @@
using QuestPDF.Drawing;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Table
{
internal class TableCellRenderingCommand
{
public TableCell Cell { get; set; }
public SpacePlan Measurement { get; set; }
public Size Size { get; set; }
public Position Offset { get; set; }
}
}
@@ -1,16 +0,0 @@
namespace QuestPDF.Elements.Table
{
internal class TableColumnDefinition
{
public float ConstantSize { get; }
public float RelativeSize { get; }
internal float Width { get; set; }
public TableColumnDefinition(float constantSize, float relativeSize)
{
ConstantSize = constantSize;
RelativeSize = relativeSize;
}
}
}
@@ -1,99 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Fluent;
namespace QuestPDF.Elements.Table
{
static class TableLayoutValidator
{
public static void PlanCellPositions(this Table table)
{
PlanCellPositions(table.Columns.Count, table.Cells);
}
private static void PlanCellPositions(int columnsCount, ICollection<TableCell> cells)
{
var cellsWindow = new List<TableCell>();
(int x, int y) currentLocation = (1, 1);
foreach (var cell in cells)
{
if (cellsWindow.Count > Math.Max(columnsCount, 16))
{
cellsWindow = cellsWindow
.Where(x => x.Row + x.RowSpan > currentLocation.y)
.ToList();
}
SetPartialLocation(cell);
if (cell.HasLocation())
{
cellsWindow.Add(cell);
currentLocation = (cell.Column, cell.Row);
continue;
}
foreach (var location in GenerateCoordinates(columnsCount, currentLocation))
{
if (location.x + cell.ColumnSpan - 1 > columnsCount)
continue;
cell.Column = location.x;
cell.Row = location.y;
if (cell.CollidesWithAnyOf(cellsWindow))
continue;
cellsWindow.Add(cell);
currentLocation = (cell.Column, cell.Row);
break;
}
}
}
private static IEnumerable<(int x, int y)> GenerateCoordinates(int columnsCount, (int x, int y) startPosition)
{
if (startPosition.x > columnsCount)
throw new ArgumentException();
foreach (var x in Enumerable.Range(startPosition.x, columnsCount - startPosition.x + 1))
yield return (x, startPosition.y);
foreach (var y in Enumerable.Range(startPosition.y + 1, 1_000_000))
foreach (var x in Enumerable.Range(1, columnsCount))
yield return (x, y);
}
private static bool CollidesWith(this TableCell cell, TableCell neighbour)
{
return cell.Column < neighbour.Column + neighbour.ColumnSpan &&
cell.Column + cell.ColumnSpan > neighbour.Column &&
cell.Row < neighbour.Row + neighbour.RowSpan &&
cell.RowSpan + cell.Row > neighbour.Row;
}
private static bool CollidesWithAnyOf(this TableCell cell, ICollection<TableCell> neighbours)
{
return neighbours.Any(cell.CollidesWith);
}
private static void SetPartialLocation(this TableCell cell)
{
if (cell.Row == default && cell.Column == default)
return;
if (cell.Row == default)
cell.Row = 1;
if (cell.Column == default)
cell.Column = 1;
}
private static bool HasLocation(this TableCell cell)
{
return cell.Row != 0 && cell.Column != 0;
}
}
}
@@ -1,41 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Drawing.Exceptions;
using QuestPDF.Fluent;
namespace QuestPDF.Elements.Table
{
static class TableLayoutPlanner
{
public static void ValidateCellPositions(this Table table)
{
ValidateCellPositions(table.Columns.Count, table.Cells);
}
private static void ValidateCellPositions(int columnsCount, ICollection<TableCell> cells)
{
const string prefix = "Detected issue in table cells configuration.";
foreach (var cell in cells)
{
if (cell.Column < 1)
throw new DocumentComposeException($"{prefix} A cell column position should be greater or equal to 1. Got {cell.Column}.");
if (cell.Row < 1)
throw new DocumentComposeException($"{prefix} A cell row position should be greater or equal to 1. Got {cell.Row}.");
if (cell.Column > columnsCount)
throw new DocumentComposeException($"{prefix} Cell starts at column that does not exist. Cell details: {GetCellDetails(cell)}.");
if (cell.Column + cell.ColumnSpan - 1 > columnsCount)
throw new DocumentComposeException($"{prefix} Table cell location is incorrect. Cell spans over columns that do not exist. Cell details: {GetCellDetails(cell)}.");
}
string GetCellDetails(TableCell cell)
{
return $"Row {cell.Row}, Column {cell.Column}, RowSpan {cell.RowSpan}, ColumnSpan {cell.ColumnSpan}";
}
}
}
}
@@ -2,8 +2,14 @@
namespace QuestPDF.Elements.Text.Calculation
{
internal struct TextDrawingRequest
internal class TextDrawingRequest
{
public ICanvas Canvas { get; set; }
public IPageContext PageContext { get; set; }
public int StartIndex { get; set; }
public int EndIndex { get; set; }
public float TotalAscent { get; set; }
public Size TextSize { get; set; }
}
@@ -5,6 +5,6 @@ namespace QuestPDF.Elements.Text.Calculation
internal class TextLineElement
{
public ITextBlockItem Item { get; set; }
public TextBlockSize Measurement { get; set; }
public TextMeasurementResult Measurement { get; set; }
}
}
@@ -0,0 +1,14 @@
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Text.Calculation
{
internal class TextMeasurementRequest
{
public ICanvas Canvas { get; set; }
public IPageContext PageContext { get; set; }
public int StartIndex { get; set; }
public float AvailableWidth { get; set; }
public bool IsFirstLineElement { get; set; }
}
}

Some files were not shown because too many files have changed in this diff Show More