Compare commits

..

1 Commits

Author SHA1 Message Date
MarcinZiabek d39819bf8d Optimized drawing border 2021-12-26 21:32:49 +01:00
71 changed files with 416 additions and 1984 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("LibreBarcode39", File.OpenRead("LibreBarcode39-Regular.ttf"));
RenderingTest
.Create()
.PageSize(400, 100)
.ShowResults()
.PageSize(300, 300)
.Render(container =>
{
container
.Background(Colors.White)
.AlignCenter()
.AlignMiddle()
.Text("*QuestPDF*", TextStyle.Default.FontType("LibreBarcode39").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());
});
});
}
}
}
@@ -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())
.Stack(stack =>
{
stack.Item().Text("Default style applies to all children", TextStyle.Default);
stack.Item().Text("You can override certain styles", TextStyle.Default.Underline(false).Color(Colors.Green.Darken2));
stack.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(30);
page.Background(Colors.White);
page.Header().Stack(stack =>
{
stack.Item().ShowOnce().Background(Colors.Blue.Lighten2).Height(60);
stack.Item().SkipOnce().Background(Colors.Green.Lighten2).Height(40);
});
page.Content().PaddingVertical(10).Stack(stack =>
{
stack.Spacing(10);
foreach (var _ in Enumerable.Range(0, 13))
stack.Item().Background(Colors.Grey.Lighten2).Height(40);
});
page.Footer().AlignCenter().Text(text =>
{
text.DefaultTextStyle(TextStyle.Default.Size(16));
text.CurrentPageNumber();
text.Span(" / ");
text.TotalPages();
});
});
});
}
}
}
+4 -4
View File
@@ -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));
@@ -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));
+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
};
}
+4 -6
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
@@ -38,7 +36,7 @@ namespace QuestPDF.Examples
.Padding(25)
.Stack(stack =>
{
for(var i = 1; i <= 4; i++)
for(var i=1; i<=4; i++)
{
stack.Item().Row(row =>
{
+2 -2
View File
@@ -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 =>
@@ -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>
-94
View File
@@ -1,94 +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 ColumnTypes()
{
RenderingTest
.Create()
.ProducePdf()
.PageSize(650, 300)
.ShowResults()
.Render(container =>
{
container
.Padding(25)
.MinimalBox()
.Border(1)
.Stack(stack =>
{
stack.Item().LabelCell("Total width: 600px");
stack.Item().Row(row =>
{
row.ConstantColumn(150).ValueCell("150px");
row.ConstantColumn(100).ValueCell("100px");
row.RelativeColumn(4).ValueCell("200px");
row.RelativeColumn(3).ValueCell("150px");
});
stack.Item().Row(row =>
{
row.ConstantColumn(100).ValueCell("100px");
row.ConstantColumn(50).ValueCell("50px");
row.RelativeColumn(3).ValueCell("300px");
row.RelativeColumn(1).ValueCell("100px");
row.ConstantColumn(50).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.RelativeColumn().Stack(stack =>
{
stack.Item().ShowOnce().Element(CreateBox).Text("X");
stack.Item().Element(CreateBox).Text("1");
stack.Item().Element(CreateBox).Text("2");
});
row.RelativeColumn().Stack(stack =>
{
stack.Item().Element(CreateBox).Text("1");
stack.Item().Element(CreateBox).Text("2");
});
});
});
static IContainer CreateBox(IContainer container)
{
return container
.ExtendHorizontal()
.ExtendVertical()
.Background(Colors.Grey.Lighten4)
.Border(1)
.AlignCenter()
.AlignMiddle()
.ShowOnce();
}
}
}
}
-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();
}
}
}
+7 -7
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)
@@ -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,7 +230,7 @@ namespace QuestPDF.Examples
container
.Padding(20)
.Padding(10)
.MinimalBox()
.Box()
.Border(1)
.Padding(5)
.Padding(10)
-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

@@ -31,12 +31,12 @@ namespace QuestPDF.ReportSample.Layouts
stack.Item().ShowOnce().Padding(5).AlignMiddle().Row(row =>
{
row.RelativeColumn(2).AlignMiddle().Text("PRIMARY HEADER", TextStyle.Default.Color(Colors.Grey.Darken3).Size(30).Bold());
row.RelativeColumn(1).AlignRight().MinimalBox().AlignMiddle().Background(Colors.Blue.Darken2).Padding(30);
row.RelativeColumn(1).AlignRight().Box().AlignMiddle().Background(Colors.Blue.Darken2).Padding(30);
});
stack.Item().SkipOnce().Padding(5).Row(row =>
{
row.RelativeColumn(2).Text("SECONDARY HEADER", TextStyle.Default.Color(Colors.Grey.Darken3).Size(30).Bold());
row.RelativeColumn(1).AlignRight().MinimalBox().Background(Colors.Blue.Lighten4).Padding(15);
row.RelativeColumn(1).AlignRight().Box().Background(Colors.Blue.Lighten4).Padding(15);
});
});
}
+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()
})
-1
View File
@@ -102,7 +102,6 @@ namespace QuestPDF.UnitTests
.DrawElement(new Size(400, 300))
.ExpectChildMeasure("left", new Size(400, 300), SpacePlan.FullRender(250, 150))
.ExpectChildDraw("left", new Size(250, 300))
.ExpectChildMeasure("right", new Size(150, 300), SpacePlan.FullRender(150, 200))
.ExpectCanvasTranslate(250, 0)
.ExpectChildDraw("right", new Size(150, 300))
.ExpectCanvasTranslate(-250, 0)
+4 -2
View File
@@ -11,13 +11,15 @@ 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 DrawImage(SKImage image, Position position, Size size) => DrawImageFunc(image, position, size);
@@ -14,7 +14,8 @@ 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 DrawImage(SKImage image, Position position, Size size) => Operations.Add(new CanvasDrawImageOperation(position, size));
@@ -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);
+19 -34
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
@@ -44,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();
@@ -61,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();
@@ -125,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));
@@ -136,7 +133,7 @@ namespace QuestPDF.Drawing
{
var debuggingState = new DebuggingState();
content.VisitChildren(x =>
content.HandleVisitor(x =>
{
x.CreateProxy(y => new DebuggingProxy(debuggingState, y));
});
@@ -144,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);
}
});
}
}
}
+6 -1
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)
{
}
+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
};
}
}
}
}
+8 -2
View File
@@ -18,14 +18,20 @@ 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)
{
+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
@@ -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)
{
+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));
+5 -4
View File
@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using QuestPDF.Drawing;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
@@ -18,10 +17,12 @@ namespace QuestPDF.Elements
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 DecorationElement;
yield return ContentElement;
DecorationElement?.HandleVisitor(visit);
ContentElement?.HandleVisitor(visit);
base.HandleVisitor(visit);
}
internal override void CreateProxy(Func<Element, Element> create)
-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;
}
}
+4 -3
View File
@@ -44,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())
+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
+2 -2
View File
@@ -40,8 +40,6 @@ namespace QuestPDF.Elements
.PaddingTop(MarginTop)
.PaddingBottom(MarginBottom)
.DefaultTextStyle(DefaultTextStyle)
.Decoration(decoration =>
{
decoration
@@ -62,6 +60,8 @@ namespace QuestPDF.Elements
.Element(Footer);
});
(container as Element).ApplyDefaultTextStyle(DefaultTextStyle);
bool IsClose(float x, float y)
{
return Math.Abs(x - y) < Size.Epsilon;
+61 -59
View File
@@ -6,16 +6,9 @@ using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class RowElement : Constrained
internal abstract class RowElement : Constrained
{
public float ConstantSize { get; }
public float RelativeSize { get; }
public RowElement(float constantSize, float relativeSize)
{
ConstantSize = constantSize;
RelativeSize = relativeSize;
}
public float Size { get; protected set; }
public void SetWidth(float width)
{
@@ -24,75 +17,75 @@ namespace QuestPDF.Elements
}
}
internal class BinaryRow : Element, ICacheable, IStateResettable
internal class ConstantRowElement : RowElement
{
public ConstantRowElement(float size)
{
Size = size;
SetWidth(size);
}
}
internal class RelativeRowElement : RowElement
{
public RelativeRowElement(float size)
{
Size = size;
}
}
internal class BinaryRow : Element, ICacheable
{
internal Element Left { get; set; }
internal Element Right { get; set; }
private bool IsLeftRendered { get; set; }
private bool IsRightRendered { get; set; }
public void ResetState()
internal override void HandleVisitor(Action<Element?> visit)
{
IsLeftRendered = false;
IsRightRendered = false;
Left.HandleVisitor(visit);
Right.HandleVisitor(visit);
base.HandleVisitor(visit);
}
internal override IEnumerable<Element?> GetChildren()
{
yield return Left;
yield return Right;
}
internal override void CreateProxy(Func<Element?, Element?> create)
{
Left = create(Left);
Right = create(Right);
}
internal override SpacePlan Measure(Size availableSpace)
{
var leftMeasurement = Left.Measure(new Size(availableSpace.Width, availableSpace.Height));
if (leftMeasurement.Type == SpacePlanType.Wrap)
return SpacePlan.Wrap();
var rightMeasurement = Right.Measure(new Size(availableSpace.Width - leftMeasurement.Width, availableSpace.Height));
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 ((!IsLeftRendered && leftMeasurement.Type == SpacePlanType.PartialRender) ||
(!IsRightRendered && rightMeasurement.Type == SpacePlanType.PartialRender))
if (leftMeasurement.Type == SpacePlanType.PartialRender || rightMeasurement.Type == SpacePlanType.PartialRender)
return SpacePlan.PartialRender(targetSize);
return SpacePlan.FullRender(targetSize);
}
internal override void Draw(Size availableSpace)
{
var leftSpace = new Size(availableSpace.Width, availableSpace.Height);
var leftMeasurement = Left.Measure(leftSpace);
if (leftMeasurement.Type == SpacePlanType.FullRender)
IsLeftRendered = true;
var leftMeasurement = Left.Measure(new Size(availableSpace.Width, availableSpace.Height));
var leftWidth = leftMeasurement.Width;
Left.Draw(new Size(leftMeasurement.Width, availableSpace.Height));
var rightSpace = new Size(availableSpace.Width - leftMeasurement.Width, availableSpace.Height);
var rightMeasurement = Right.Measure(rightSpace);
Left.Draw(new Size(leftWidth, availableSpace.Height));
if (rightMeasurement.Type == SpacePlanType.FullRender)
IsRightRendered = true;
Canvas.Translate(new Position(leftMeasurement.Width, 0));
Right.Draw(rightSpace);
Canvas.Translate(new Position(-leftMeasurement.Width, 0));
Canvas.Translate(new Position(leftWidth, 0));
Right.Draw(new Size(availableSpace.Width - leftWidth, availableSpace.Height));
Canvas.Translate(new Position(-leftWidth, 0));
}
}
@@ -100,15 +93,16 @@ namespace QuestPDF.Elements
{
public float Spacing { get; set; } = 0;
public ICollection<RowElement> Items { get; internal set; } = new List<RowElement>();
public ICollection<RowElement> Children { get; internal set; } = new List<RowElement>();
private Element? RootElement { get; set; }
internal override IEnumerable<Element?> GetChildren()
internal override void HandleVisitor(Action<Element?> visit)
{
if (RootElement == null)
ComposeTree();
yield return RootElement;
RootElement.HandleVisitor(visit);
base.HandleVisitor(visit);
}
internal override SpacePlan Measure(Size availableSpace)
@@ -127,23 +121,31 @@ namespace QuestPDF.Elements
private void ComposeTree()
{
Items = AddSpacing(Items, Spacing);
Children = AddSpacing(Children, Spacing);
var elements = Items.Cast<Element>().ToArray();
var elements = Children.Cast<Element>().ToArray();
RootElement = BuildTree(elements);
}
private void UpdateElementsWidth(float availableWidth)
{
var constantWidth = Items.Sum(x => x.ConstantSize);
var relativeWidth = Items.Sum(x => x.RelativeSize);
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 = (relativeWidth > 0) ? (availableWidth - constantWidth) / relativeWidth : 0;
var widthPerRelativeUnit = (availableWidth - constantWidth) / relativeWidth;
foreach (var row in Items)
{
row.SetWidth(row.ConstantSize + row.RelativeSize * widthPerRelativeUnit);
}
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)
@@ -152,7 +154,7 @@ namespace QuestPDF.Elements
return elements;
return elements
.SelectMany(x => new[] { new RowElement(spacing, 0), x })
.SelectMany(x => new[] { new ConstantRowElement(spacing), x })
.Skip(1)
.ToList();
}
+8 -6
View File
@@ -16,12 +16,14 @@ namespace QuestPDF.Elements
internal bool IsFirstRendered { get; set; } = false;
internal override IEnumerable<Element?> GetChildren()
internal override void HandleVisitor(Action<Element?> visit)
{
yield return First;
yield return Second;
First?.HandleVisitor(visit);
Second?.HandleVisitor(visit);
base.HandleVisitor(visit);
}
public void ResetState()
{
IsFirstRendered = false;
@@ -95,12 +97,12 @@ namespace QuestPDF.Elements
internal class Stack : IComponent
{
public ICollection<Element> Items { get; } = new List<Element>();
public ICollection<Element> Children { get; } = new List<Element>();
public float Spacing { get; set; } = 0;
public void Compose(IContainer container)
{
var elements = AddSpacing(Spacing, Items);
var elements = AddSpacing(Spacing, Children);
container
.PaddingBottom(-Spacing)
@@ -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}";
}
}
}
}
@@ -1,6 +1,5 @@
using QuestPDF.Drawing;
using QuestPDF.Elements.Text.Calculation;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Text.Items
@@ -11,8 +10,8 @@ namespace QuestPDF.Elements.Text.Items
public TextMeasurementResult? Measure(TextMeasurementRequest request)
{
Element.VisitChildren(x => (x as IStateResettable)?.ResetState());
Element.VisitChildren(x => x.Initialize(request.PageContext, request.Canvas));
Element.HandleVisitor(x => (x as IStateResettable)?.ResetState());
Element.HandleVisitor(x => x.Initialize(request.PageContext, request.Canvas));
var measurement = Element.Measure(new Size(request.AvailableWidth, Size.Max.Height));
@@ -36,8 +35,8 @@ namespace QuestPDF.Elements.Text.Items
public void Draw(TextDrawingRequest request)
{
Element.VisitChildren(x => (x as IStateResettable)?.ResetState());
Element.VisitChildren(x => x.Initialize(request.PageContext, request.Canvas));
Element.HandleVisitor(x => (x as IStateResettable)?.ResetState());
Element.HandleVisitor(x => x.Initialize(request.PageContext, request.Canvas));
request.Canvas.Translate(new Position(0, request.TotalAscent));
Element.Draw(new Size(request.TextSize.Width, -request.TotalAscent));
@@ -111,7 +111,7 @@ namespace QuestPDF.Elements.Text.Items
var text = Text.Substring(request.StartIndex, request.EndIndex - request.StartIndex);
request.Canvas.DrawRectangle(new Position(0, request.TotalAscent), new Size(request.TextSize.Width, request.TextSize.Height), Style.BackgroundColor);
request.Canvas.DrawFilledRectangle(new Position(0, request.TotalAscent), new Size(request.TextSize.Width, request.TextSize.Height), Style.BackgroundColor);
request.Canvas.DrawText(text, Position.Zero, Style);
// draw underline
@@ -124,7 +124,7 @@ namespace QuestPDF.Elements.Text.Items
void DrawLine(float offset, float thickness)
{
request.Canvas.DrawRectangle(new Position(0, offset - thickness / 2f), new Size(request.TextSize.Width, thickness), Style.Color);
request.Canvas.DrawFilledRectangle(new Position(0, offset - thickness / 2f), new Size(request.TextSize.Width, thickness), Style.Color);
}
}
}
+4 -4
View File
@@ -11,16 +11,16 @@ namespace QuestPDF.Elements.Text
internal class TextBlock : Element, IStateResettable
{
public HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
public List<ITextBlockItem> Items { get; set; } = new List<ITextBlockItem>();
public List<ITextBlockItem> Children { get; set; } = new List<ITextBlockItem>();
public string Text => string.Join(" ", Items.Where(x => x is TextBlockSpan).Cast<TextBlockSpan>().Select(x => x.Text));
public string Text => string.Join(" ", Children.Where(x => x is TextBlockSpan).Cast<TextBlockSpan>().Select(x => x.Text));
private Queue<ITextBlockItem> RenderingQueue { get; set; }
private int CurrentElementIndex { get; set; }
public void ResetState()
{
RenderingQueue = new Queue<ITextBlockItem>(Items);
RenderingQueue = new Queue<ITextBlockItem>(Children);
CurrentElementIndex = 0;
}
@@ -32,7 +32,7 @@ namespace QuestPDF.Elements.Text
var lines = DivideTextItemsIntoLines(availableSpace.Width, availableSpace.Height).ToList();
if (!lines.Any())
return SpacePlan.Wrap();
return SpacePlan.PartialRender(Size.Zero);
var width = lines.Max(x => x.Width);
var height = lines.Sum(x => x.LineHeight);
+1 -16
View File
@@ -136,29 +136,14 @@ namespace QuestPDF.Fluent
});
}
// TODO: deprecated Box method in QuestPDF 2022.1
[Obsolete("This element has been renamed. Please use the MinimalBox method.")]
public static IContainer Box(this IContainer element)
{
return element.Element(new MinimalBox());
}
public static IContainer MinimalBox(this IContainer element)
{
return element.Element(new MinimalBox());
return element.Element(new Box());
}
public static IContainer Unconstrained(this IContainer element)
{
return element.Element(new Unconstrained());
}
public static IContainer DefaultTextStyle(this IContainer element, TextStyle textStyle)
{
return element.Element(new DefaultTextStyle
{
TextStyle = textStyle
});
}
}
}
+6 -8
View File
@@ -15,19 +15,17 @@ namespace QuestPDF.Fluent
public IContainer ConstantColumn(float width)
{
return Column(constantWidth: width);
var element = new ConstantRowElement(width);
Row.Children.Add(element);
return element;
}
public IContainer RelativeColumn(float width = 1)
{
return Column(relativeWidth: width);
}
private IContainer Column(float constantWidth = 0, float relativeWidth = 0)
{
var element = new RowElement(constantWidth, relativeWidth);
var element = new RelativeRowElement(width);
Row.Items.Add(element);
Row.Children.Add(element);
return element;
}
}
+1 -1
View File
@@ -16,7 +16,7 @@ namespace QuestPDF.Fluent
public IContainer Item()
{
var container = new Container();
Stack.Items.Add(container);
Stack.Children.Add(container);
return container;
}
}
-162
View File
@@ -1,162 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using QuestPDF.Drawing.Exceptions;
using QuestPDF.Elements;
using QuestPDF.Elements.Table;
using QuestPDF.Infrastructure;
namespace QuestPDF.Fluent
{
public class TableColumnsDefinitionDescriptor
{
internal List<TableColumnDefinition> Columns { get; } = new();
public void ConstantColumn(float width)
{
ComplexColumn(constantWidth: width);
}
public void RelativeColumn(float width = 1)
{
ComplexColumn(relativeWidth: width);
}
public void ComplexColumn(float constantWidth = 0, float relativeWidth = 0)
{
var columnDefinition = new TableColumnDefinition(constantWidth, relativeWidth);
Columns.Add(columnDefinition);
}
}
public class TableCellDescriptor
{
private ICollection<TableCell> Cells { get; }
internal TableCellDescriptor(ICollection<TableCell> cells)
{
Cells = cells;
}
public ITableCellContainer Cell()
{
var cell = new TableCell();
Cells.Add(cell);
return cell;
}
}
public class TableDescriptor
{
internal List<TableColumnDefinition> Columns { get; private set; }
private Table HeaderTable { get; } = new();
private Table ContentTable { get; } = new();
private Table FooterTable { get; } = new();
public void ColumnsDefinition(Action<TableColumnsDefinitionDescriptor> handler)
{
var descriptor = new TableColumnsDefinitionDescriptor();
handler(descriptor);
HeaderTable.Columns = descriptor.Columns;
ContentTable.Columns = descriptor.Columns;
FooterTable.Columns = descriptor.Columns;
}
public void ExtendLastCellsToTableBottom()
{
ContentTable.ExtendLastCellsToTableBottom = true;
}
public void Header(Action<TableCellDescriptor> handler)
{
var descriptor = new TableCellDescriptor(HeaderTable.Cells);
handler(descriptor);
}
public void Footer(Action<TableCellDescriptor> handler)
{
var descriptor = new TableCellDescriptor(FooterTable.Cells);
handler(descriptor);
}
public ITableCellContainer Cell()
{
var cell = new TableCell();
ContentTable.Cells.Add(cell);
return cell;
}
internal IElement CreateElement()
{
var container = new Container();
ConfigureTable(HeaderTable);
ConfigureTable(ContentTable);
ConfigureTable(FooterTable);
container
.Decoration(decoration =>
{
decoration.Header().Element(HeaderTable);
decoration.Content().Element(ContentTable);
decoration.Footer().Element(FooterTable);
});
return container;
}
private static void ConfigureTable(Table table)
{
if (!table.Columns.Any())
throw new DocumentComposeException($"Table should have at least one column. Please call the '{nameof(ColumnsDefinition)}' method to define columns.");
table.PlanCellPositions();
table.ValidateCellPositions();
}
}
public static class TableExtensions
{
public static void Table(this IContainer element, Action<TableDescriptor> handler)
{
var descriptor = new TableDescriptor();
handler(descriptor);
element.Element(descriptor.CreateElement());
}
}
public static class TableCellExtensions
{
private static ITableCellContainer TableCell(this ITableCellContainer element, Action<TableCell> handler)
{
if (element is TableCell tableCell)
handler(tableCell);
return element;
}
public static ITableCellContainer Column(this ITableCellContainer tableCellContainer, uint value)
{
return tableCellContainer.TableCell(x => x.Column = (int)value);
}
public static ITableCellContainer ColumnSpan(this ITableCellContainer tableCellContainer, uint value)
{
return tableCellContainer.TableCell(x => x.ColumnSpan = (int)value);
}
public static ITableCellContainer Row(this ITableCellContainer tableCellContainer, uint value)
{
return tableCellContainer.TableCell(x => x.Row = (int)value);
}
public static ITableCellContainer RowSpan(this ITableCellContainer tableCellContainer, uint value)
{
return tableCellContainer.TableCell(x => x.RowSpan = (int)value);
}
}
}
+7 -4
View File
@@ -47,7 +47,7 @@ namespace QuestPDF.Fluent
if (!TextBlocks.Any())
TextBlocks.Add(new TextBlock());
TextBlocks.Last().Items.Add(item);
TextBlocks.Last().Children.Add(item);
}
public void Span(string? text, TextStyle? style = null)
@@ -73,7 +73,7 @@ namespace QuestPDF.Fluent
.Skip(1)
.Select(x => new TextBlock
{
Items = new List<ITextBlockItem> { x }
Children = new List<ITextBlockItem> { x }
})
.ToList()
.ForEach(TextBlocks.Add);
@@ -167,14 +167,17 @@ namespace QuestPDF.Fluent
Element = container
});
return container.AlignBottom().MinimalBox();
return container.AlignBottom().Box();
}
internal void Compose(IContainer container)
{
TextBlocks.ToList().ForEach(x => x.Alignment = Alignment);
foreach (var textBlockSpan in TextBlocks.SelectMany(x => x.Children).Where(x => x is TextBlockSpan).Cast<TextBlockSpan>())
textBlockSpan.Style.ApplyParentStyle(DefaultStyle);
container.DefaultTextStyle(DefaultStyle).Stack(stack =>
container.Stack(stack =>
{
stack.Spacing(Spacing);
-10
View File
@@ -1,10 +1,8 @@
using System;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text.RegularExpressions;
using QuestPDF.Infrastructure;
namespace QuestPDF.Helpers
{
@@ -43,13 +41,5 @@ namespace QuestPDF.Helpers
{
return Regex.Replace(text, @"([a-z])([A-Z])", "$1 $2");
}
internal static void VisitChildren(this Element? element, Action<Element?> handler)
{
foreach (var child in element.GetChildren().Where(x => x != null))
VisitChildren(child, handler);
handler(element);
}
}
}
+2 -2
View File
@@ -50,8 +50,8 @@ namespace QuestPDF.Helpers
public static PageSize EnvDL => new PageSize(311.9f, 623.7f);
public static PageSize Executive => new PageSize(522, 756);
public static PageSize Legal => new PageSize(612, 1008);
public static PageSize Letter => new PageSize(612, 792);
public static PageSize Legal => new PageSize(612.4f, 1009.3f);
public static PageSize Letter => new PageSize(612.4f, 791);
public static PageSize ARCH_A => new PageSize(649.2f, 864.7f);
public static PageSize ARCH_B => new PageSize(864.7f, 1295.6f);
+1 -1
View File
@@ -7,7 +7,7 @@ namespace QuestPDF.Helpers
{
public static class Placeholders
{
public static readonly Random Random = new();
public static readonly Random Random = new Random();
#region Word Cache
+3 -3
View File
@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using QuestPDF.Drawing;
using QuestPDF.Elements;
@@ -15,9 +14,10 @@ namespace QuestPDF.Infrastructure
set => Child = value as Element;
}
internal override IEnumerable<Element?> GetChildren()
internal override void HandleVisitor(Action<Element?> visit)
{
yield return Child;
Child?.HandleVisitor(visit);
base.HandleVisitor(visit);
}
internal override void CreateProxy(Func<Element?, Element?> create)
+8 -3
View File
@@ -10,12 +10,12 @@ namespace QuestPDF.Infrastructure
internal IPageContext PageContext { get; set; }
internal ICanvas Canvas { get; set; }
internal virtual IEnumerable<Element?> GetChildren()
internal virtual void HandleVisitor(Action<Element?> visit)
{
yield break;
visit(this);
}
internal virtual void Initialize(IPageContext pageContext, ICanvas canvas)
internal void Initialize(IPageContext pageContext, ICanvas canvas)
{
PageContext = pageContext;
Canvas = canvas;
@@ -28,5 +28,10 @@ namespace QuestPDF.Infrastructure
internal abstract SpacePlan Measure(Size availableSpace);
internal abstract void Draw(Size availableSpace);
protected virtual IEnumerable<string> GetDebugInformation()
{
yield break;
}
}
}
+2 -1
View File
@@ -6,7 +6,8 @@ namespace QuestPDF.Infrastructure
{
void Translate(Position vector);
void DrawRectangle(Position vector, Size size, string color);
void DrawFilledRectangle(Position vector, Size size, string color);
void DrawStrokedRectangle(Size size, string color, float width);
void DrawText(string text, Position position, TextStyle style);
void DrawImage(SKImage image, Position position, Size size);
-2
View File
@@ -17,7 +17,5 @@
{
return new Position(-X, -Y);
}
public override string ToString() => $"(Left: {X:N3}, Top: {Y:N3})";
}
}
+1 -2
View File
@@ -3,7 +3,6 @@
public readonly struct Size
{
public const float Epsilon = 0.001f;
public const float Infinity = float.PositiveInfinity;
public readonly float Width;
public readonly float Height;
@@ -17,6 +16,6 @@
Height = height;
}
public override string ToString() => $"(Width: {Width:N3}, Height: {Height:N3})";
public override string ToString() => $"(Width: {Width}, Height: {Height})";
}
}
+2 -2
View File
@@ -4,10 +4,10 @@
<Authors>MarcinZiabek</Authors>
<Company>CodeFlint</Company>
<PackageId>QuestPDF</PackageId>
<Version>2022.1.0</Version>
<Version>2021.12.0</Version>
<PackageDescription>QuestPDF is an open-source, modern and battle-tested library that can help you with generating PDF documents by offering friendly, discoverable and predictable C# fluent API.</PackageDescription>
<PackageReleaseNotes>$([System.IO.File]::ReadAllText("$(MSBuildProjectDirectory)/Resources/ReleaseNotes.txt"))</PackageReleaseNotes>
<LangVersion>9</LangVersion>
<LangVersion>8</LangVersion>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageIcon>Logo.png</PackageIcon>
<PackageIconUrl>https://www.questpdf.com/images/package-logo.png</PackageIconUrl>
+4 -4
View File
@@ -1,4 +1,4 @@
- Introduced new element: `Table` - a great way to construct complex document structures, e.g. reports. This element covers all cases offered by combination of the `Stack` and the `Row` elements. Additionally, it provides support for more complex layouts and corner cases. Updating to the `Table` element can greatly simplify your code 😁
- Added new element `DefaultTextStyle` - it allows set new text style to all its children,
- Improved the default paging behavior for the `Row` element. In some minor corner cases it might cause infinite layout exceptions and confuse developers.
- Fixed default page sizes for: Letter and Legal.
- Improved debugging experience for layout-related exceptions. To make the library predictable, it is (by design) very strict about layouting rules and throws an exception when a constraint cannot be met. In this release, each exception contains an element stack that contains all information needed to identify the issue. By default, this feature is enabled only when debugger is attached.
- Improved layouting algorithm performance by introducing additional caching layer. This cache reduces the layouting time by half. By default, this feature is enabled only when debugger is not attached (mostly release mode).
- Reduced GA pressure put by the layouting algorithm. Previously, every element measurement operation was represented by an object and the paging support was done via class hierarchy. New solution uses structs (which are value-types) and enums. This also makes the code more readable and easier to follow.
- Added support for generating XPS files which are easier to print in the Windows environment. This was possible due to existing support in SkiaSharp. This change was proposed by **sbrkich**, thank you!