Compare commits
36 Commits
element-proxy
...
2022.2
| Author | SHA1 | Date | |
|---|---|---|---|
| ba409a8712 | |||
| e3ce194fd1 | |||
| 9b892c86ed | |||
| c9833d410d | |||
| 763fdfac0f | |||
| 630d3389d3 | |||
| a7c3c88e81 | |||
| 9524cc6eb1 | |||
| 4152b67679 | |||
| 8020cb30cb | |||
| 9358fb8937 | |||
| eb297177ea | |||
| 7f651932b9 | |||
| caaae5fa1c | |||
| f76caa28b1 | |||
| ea92655ef5 | |||
| 3f8463e9b7 | |||
| 02e25b193d | |||
| 7cc272ff9e | |||
| b8ea30be23 | |||
| 06418238dc | |||
| ed73c5b82d | |||
| 96732b5856 | |||
| c7008ad940 | |||
| 940616cd3e | |||
| 01a80032b0 | |||
| 3e5e3843d2 | |||
| d3911d5687 | |||
| 82e4dd36f0 | |||
| 561d6a004c | |||
| 59b77e55d4 | |||
| ea398c562f | |||
| 5f636beb9d | |||
| 5b85ae2627 | |||
| 10683776bb | |||
| f2c47c4fd3 |
@@ -0,0 +1,70 @@
|
||||
# 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
|
||||
@@ -1,32 +0,0 @@
|
||||
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));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using NUnit.Framework;
|
||||
using System.IO;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
@@ -6,26 +8,25 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
[TestFixture]
|
||||
public class BarcodeExamples
|
||||
{
|
||||
[Test]
|
||||
public void Barcode()
|
||||
public void Example()
|
||||
{
|
||||
FontManager.RegisterFontType("LibreBarcode39", File.OpenRead("LibreBarcode39-Regular.ttf"));
|
||||
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 300)
|
||||
.PageSize(400, 100)
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.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));
|
||||
});
|
||||
.Background(Colors.White)
|
||||
.AlignCenter()
|
||||
.AlignMiddle()
|
||||
.Text("*QuestPDF*", TextStyle.Default.FontType("LibreBarcode39").Size(64));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
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,3 +1,4 @@
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
@@ -43,19 +44,41 @@ namespace QuestPDF.Examples
|
||||
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 300)
|
||||
.PageSize(400, 600)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container.Extend().Canvas((canvas, size) =>
|
||||
{
|
||||
var bar = new BarChart
|
||||
container
|
||||
.Background(Colors.White)
|
||||
.Padding(25)
|
||||
.Stack(stack =>
|
||||
{
|
||||
Entries = entries,
|
||||
IsAnimated = false,
|
||||
};
|
||||
bar.Draw(canvas, (int)size.Width, (int)size.Height);
|
||||
});
|
||||
stack
|
||||
.Item()
|
||||
.PaddingBottom(10)
|
||||
.Text("Chart example", TextStyle.Default.Size(20).SemiBold().Color(Colors.Blue.Medium));
|
||||
|
||||
stack
|
||||
.Item()
|
||||
.Border(1)
|
||||
.ExtendHorizontal()
|
||||
.Height(300)
|
||||
.Canvas((canvas, size) =>
|
||||
{
|
||||
var chart = new BarChart
|
||||
{
|
||||
Entries = entries,
|
||||
|
||||
LabelOrientation = Orientation.Horizontal,
|
||||
ValueLabelOrientation = Orientation.Horizontal,
|
||||
|
||||
IsAnimated = false,
|
||||
};
|
||||
|
||||
chart.DrawContent(canvas, (int)size.Width, (int)size.Height);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,7 @@ namespace QuestPDF.Examples
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProducePdf()
|
||||
.ShowResults()
|
||||
.Render(x => x.Image(new byte[] { 1, 2, 3 }));
|
||||
//.Render(x => GenerateStructure(x, 16));
|
||||
.Render(x => GenerateStructure(x, 16));
|
||||
}
|
||||
|
||||
private void GenerateStructure(IContainer container, int level)
|
||||
@@ -33,7 +32,7 @@ namespace QuestPDF.Examples
|
||||
if (level % 3 == 0)
|
||||
{
|
||||
container
|
||||
.Border(level / 10f)
|
||||
.Border(level / 4f)
|
||||
.BorderColor(Colors.Black)
|
||||
.Row(row =>
|
||||
{
|
||||
@@ -44,7 +43,7 @@ namespace QuestPDF.Examples
|
||||
else
|
||||
{
|
||||
container
|
||||
.Border(level / 10f)
|
||||
.Border(level / 4f)
|
||||
.BorderColor(Colors.Black)
|
||||
.Stack(stack =>
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Drawing.Exceptions;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
@@ -10,23 +11,66 @@ namespace QuestPDF.Examples
|
||||
[Test]
|
||||
public void Stack()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(500, 360)
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Background(Colors.White)
|
||||
.Padding(15)
|
||||
.Grid(grid =>
|
||||
{
|
||||
grid.Spacing(15);
|
||||
|
||||
grid.Item().Background(Colors.Grey.Medium).Text(Placeholders.LoremIpsum());
|
||||
grid.Item().DebugPointer("Test").Background(Colors.Grey.Lighten1).Height(1000); // <-- problem
|
||||
grid.Item().Background(Colors.Grey.Lighten2).Height(150);
|
||||
});
|
||||
});
|
||||
Assert.Throws<DocumentLayoutException>(() =>
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(500, 360)
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(10)
|
||||
.Width(100)
|
||||
.Background(Colors.Grey.Lighten3)
|
||||
.DebugPointer("Example debug pointer")
|
||||
.Stack(x =>
|
||||
{
|
||||
x.Item().Text("Test");
|
||||
x.Item().Width(150);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Simple()
|
||||
{
|
||||
Assert.Throws<DocumentLayoutException>(() =>
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(500, 360)
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(10)
|
||||
.Width(100)
|
||||
.Background(Colors.Grey.Lighten3)
|
||||
.Width(150)
|
||||
.Text("Test");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DebugPointer()
|
||||
{
|
||||
Assert.Throws<DocumentLayoutException>(() =>
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(500, 360)
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Background(Colors.Grey.Lighten3)
|
||||
.Padding(10)
|
||||
.Width(100)
|
||||
.DebugPointer("Example debug pointer")
|
||||
.Width(150)
|
||||
.Text("Example");
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
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));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -474,7 +474,7 @@ namespace QuestPDF.Examples
|
||||
.Padding(15)
|
||||
.Border(4)
|
||||
.BorderColor(Colors.Blue.Medium)
|
||||
//.Box()
|
||||
//.MinimalBox()
|
||||
.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")
|
||||
.Box()
|
||||
.MinimalBox()
|
||||
|
||||
.Padding(25)
|
||||
|
||||
@@ -588,7 +588,7 @@ namespace QuestPDF.Examples
|
||||
|
||||
return element;
|
||||
})
|
||||
.Box()
|
||||
.MinimalBox()
|
||||
.Background(Colors.White)
|
||||
.Padding(10)
|
||||
.Text($"Rotated {turns * 90}°", TextStyle.Default.Size(16));
|
||||
@@ -686,7 +686,7 @@ namespace QuestPDF.Examples
|
||||
|
||||
return element;
|
||||
})
|
||||
.Box()
|
||||
.MinimalBox()
|
||||
.Background(Colors.White)
|
||||
.Padding(10)
|
||||
.Text($"Flipped {turns}", TextStyle.Default.Size(16));
|
||||
|
||||
@@ -20,6 +20,8 @@ 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()
|
||||
@@ -67,6 +69,24 @@ 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 =>
|
||||
@@ -78,16 +98,11 @@ 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);
|
||||
var document = new SimpleDocument(content, MaxPagesThreshold.Value, ApplyCaching, ApplyDebugging);
|
||||
|
||||
Render(document);
|
||||
}
|
||||
|
||||
@@ -13,11 +13,15 @@ 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)
|
||||
public SimpleDocument(Action<IDocumentContainer> content, int maxPages, bool applyCaching, bool applyDebugging)
|
||||
{
|
||||
Content = content;
|
||||
MaxPages = maxPages;
|
||||
ApplyCaching = applyCaching;
|
||||
ApplyDebugging = applyDebugging;
|
||||
}
|
||||
|
||||
public DocumentMetadata GetMetadata()
|
||||
@@ -25,7 +29,9 @@ namespace QuestPDF.Examples.Engine
|
||||
return new DocumentMetadata()
|
||||
{
|
||||
RasterDpi = PageSizes.PointsPerInch * ImageScalingFactor,
|
||||
DocumentLayoutExceptionThreshold = MaxPages
|
||||
DocumentLayoutExceptionThreshold = MaxPages,
|
||||
ApplyCaching = ApplyCaching,
|
||||
ApplyDebugging = ApplyDebugging
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -12,12 +12,14 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
return container
|
||||
.Border(1)
|
||||
.Background(dark ? Colors.Grey.Lighten2 : Colors.White)
|
||||
.Padding(10);
|
||||
.BorderColor(Colors.Grey.Medium)
|
||||
.Background(dark ? Colors.Grey.Lighten3 : Colors.White)
|
||||
.Padding(5);
|
||||
}
|
||||
|
||||
public static void LabelCell(this IContainer container, string text) => container.Cell(true).Text(text, TextStyle.Default.Medium());
|
||||
public static void LabelCell(this IContainer container, string text) => container.Cell(true).Text(text, TextStyle.Default.SemiBold());
|
||||
public static IContainer ValueCell(this IContainer container) => container.Cell(false);
|
||||
public static void ValueCell(this IContainer container, string text) => container.ValueCell().Text(text, TextStyle.Default);
|
||||
}
|
||||
|
||||
public class FrameExample
|
||||
@@ -36,7 +38,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 =>
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.IO;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Drawing.Exceptions;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
@@ -36,12 +37,15 @@ namespace QuestPDF.Examples
|
||||
[Test]
|
||||
public void Exception()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A5)
|
||||
.ProducePdf()
|
||||
.ShowResults()
|
||||
.Render(page => page.Image("non_existent.png"));
|
||||
Assert.Throws<DocumentComposeException>(() =>
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A2)
|
||||
.ProducePdf()
|
||||
.ShowResults()
|
||||
.Render(page => page.Image("non_existent.png"));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ namespace QuestPDF.Examples
|
||||
decoration
|
||||
.Content()
|
||||
.PaddingTop(25)
|
||||
//.Box()
|
||||
//.MinimalBox()
|
||||
.Border(1)
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
.Inlined(inlined =>
|
||||
@@ -94,7 +94,7 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
container
|
||||
.Padding(20)
|
||||
.Box()
|
||||
.MinimalBox()
|
||||
.Border(1)
|
||||
.Background(Colors.Grey.Lighten4)
|
||||
.Inlined(inlined =>
|
||||
|
||||
@@ -36,6 +36,8 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(350, 280)
|
||||
.ProducePdf()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
|
||||
@@ -6,11 +6,13 @@
|
||||
</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>
|
||||
@@ -27,6 +29,9 @@
|
||||
<None Update="logo.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="pdf-icon.svg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,500 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
container
|
||||
.Padding(5)
|
||||
.Box()
|
||||
.MinimalBox()
|
||||
.Border(1)
|
||||
.Padding(10)
|
||||
.Text(text =>
|
||||
@@ -48,7 +48,7 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
container
|
||||
.Padding(5)
|
||||
.Box()
|
||||
.MinimalBox()
|
||||
.Border(1)
|
||||
.Padding(10)
|
||||
.Text(text =>
|
||||
@@ -76,7 +76,7 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
container
|
||||
.Padding(5)
|
||||
.Box()
|
||||
.MinimalBox()
|
||||
.Border(1)
|
||||
.Padding(10)
|
||||
.Text(text =>
|
||||
@@ -108,7 +108,7 @@ namespace QuestPDF.Examples
|
||||
container
|
||||
.Padding(20)
|
||||
.Padding(10)
|
||||
.Box()
|
||||
.MinimalBox()
|
||||
.Border(1)
|
||||
.Padding(5)
|
||||
.Padding(10)
|
||||
@@ -138,7 +138,7 @@ namespace QuestPDF.Examples
|
||||
container
|
||||
.Padding(20)
|
||||
.Padding(10)
|
||||
.Box()
|
||||
.MinimalBox()
|
||||
.Border(1)
|
||||
.Padding(5)
|
||||
.Padding(10)
|
||||
@@ -167,7 +167,7 @@ namespace QuestPDF.Examples
|
||||
container
|
||||
.Padding(20)
|
||||
.Padding(10)
|
||||
.Box()
|
||||
.MinimalBox()
|
||||
.Border(1)
|
||||
.Padding(5)
|
||||
.Padding(10)
|
||||
@@ -230,7 +230,7 @@ namespace QuestPDF.Examples
|
||||
container
|
||||
.Padding(20)
|
||||
.Padding(10)
|
||||
.Box()
|
||||
.MinimalBox()
|
||||
.Border(1)
|
||||
.Padding(5)
|
||||
.Padding(10)
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
<?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 "PDF":
|
||||
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>
|
||||
|
After 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().Box().AlignMiddle().Background(Colors.Blue.Darken2).Padding(30);
|
||||
row.RelativeColumn(1).AlignRight().MinimalBox().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().Box().Background(Colors.Blue.Lighten4).Padding(15);
|
||||
row.RelativeColumn(1).AlignRight().MinimalBox().Background(Colors.Blue.Lighten4).Padding(15);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ using NUnit.Framework;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Drawing.Proxy;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
using QuestPDF.ReportSample.Layouts;
|
||||
|
||||
@@ -55,7 +56,7 @@ namespace QuestPDF.ReportSample
|
||||
var sw = new Stopwatch();
|
||||
sw.Start();
|
||||
|
||||
Content.HandleVisitor(x =>
|
||||
Content.VisitChildren(x =>
|
||||
{
|
||||
if (x is ICacheable)
|
||||
x.CreateProxy(y => new CacheProxy(y));
|
||||
|
||||
@@ -24,6 +24,8 @@ 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);
|
||||
|
||||
@@ -10,13 +10,13 @@ namespace QuestPDF.UnitTests
|
||||
public class BoxTests
|
||||
{
|
||||
[Test]
|
||||
public void Measure() => SimpleContainerTests.Measure<Box>();
|
||||
public void Measure() => SimpleContainerTests.Measure<MinimalBox>();
|
||||
|
||||
[Test]
|
||||
public void Draw_Wrap()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Box
|
||||
.For(x => new MinimalBox
|
||||
{
|
||||
Child = x.CreateChild()
|
||||
})
|
||||
@@ -29,7 +29,7 @@ namespace QuestPDF.UnitTests
|
||||
public void Measure_PartialRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Box
|
||||
.For(x => new MinimalBox
|
||||
{
|
||||
Child = x.CreateChild()
|
||||
})
|
||||
@@ -43,7 +43,7 @@ namespace QuestPDF.UnitTests
|
||||
public void Measure_FullRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Box
|
||||
.For(x => new MinimalBox
|
||||
{
|
||||
Child = x.CreateChild()
|
||||
})
|
||||
|
||||
@@ -102,6 +102,7 @@ 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)
|
||||
|
||||
@@ -213,7 +213,7 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
|
||||
public TestPlan CheckMeasureResult(SpacePlan expected)
|
||||
{
|
||||
Element.HandleVisitor(x => x?.Initialize(null, Canvas));
|
||||
Element.VisitChildren(x => x?.Initialize(null, Canvas));
|
||||
|
||||
var actual = Element.Measure(OperationInput);
|
||||
|
||||
@@ -228,7 +228,7 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
|
||||
public TestPlan CheckDrawResult()
|
||||
{
|
||||
Element.HandleVisitor(x => x?.Initialize(null, Canvas));
|
||||
Element.VisitChildren(x => x?.Initialize(null, Canvas));
|
||||
Element.Draw(OperationInput);
|
||||
return this;
|
||||
}
|
||||
@@ -271,10 +271,10 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
availableSpace ??= new Size(400, 300);
|
||||
|
||||
var canvas = new FreeCanvas();
|
||||
value.HandleVisitor(x => x.Initialize(null, canvas));
|
||||
value.VisitChildren(x => x.Initialize(null, canvas));
|
||||
var valueMeasure = value.Measure(availableSpace.Value);
|
||||
|
||||
expected.HandleVisitor(x => x.Initialize(null, canvas));
|
||||
expected.VisitChildren(x => x.Initialize(null, canvas));
|
||||
var expectedMeasure = expected.Measure(availableSpace.Value);
|
||||
|
||||
valueMeasure.Should().BeEquivalentTo(expectedMeasure);
|
||||
@@ -285,11 +285,11 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
availableSpace ??= new Size(400, 300);
|
||||
|
||||
var valueCanvas = new OperationRecordingCanvas();
|
||||
value.HandleVisitor(x => x.Initialize(null, valueCanvas));
|
||||
value.VisitChildren(x => x.Initialize(null, valueCanvas));
|
||||
value.Draw(availableSpace.Value);
|
||||
|
||||
var expectedCanvas = new OperationRecordingCanvas();
|
||||
expected.HandleVisitor(x => x.Initialize(null, expectedCanvas));
|
||||
expected.VisitChildren(x => x.Initialize(null, expectedCanvas));
|
||||
expected.Draw(availableSpace.Value);
|
||||
|
||||
valueCanvas.Operations.Should().BeEquivalentTo(expectedCanvas.Operations);
|
||||
|
||||
@@ -7,6 +7,8 @@ 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
|
||||
@@ -42,20 +44,15 @@ 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();
|
||||
|
||||
DebuggingState debuggingState = null;
|
||||
|
||||
if (System.Diagnostics.Debugger.IsAttached)
|
||||
{
|
||||
debuggingState = ApplyDebugging(content);
|
||||
}
|
||||
else
|
||||
{
|
||||
var debuggingState = metadata.ApplyDebugging ? ApplyDebugging(content) : null;
|
||||
|
||||
if (metadata.ApplyCaching)
|
||||
ApplyCaching(content);
|
||||
}
|
||||
|
||||
RenderPass(pageContext, new FreeCanvas(), content, metadata, debuggingState);
|
||||
RenderPass(pageContext, canvas, content, metadata, debuggingState);
|
||||
@@ -64,8 +61,8 @@ namespace QuestPDF.Drawing
|
||||
internal static void RenderPass<TCanvas>(PageContext pageContext, TCanvas canvas, Container content, DocumentMetadata documentMetadata, DebuggingState? debuggingState)
|
||||
where TCanvas : ICanvas, IRenderingCanvas
|
||||
{
|
||||
content.HandleVisitor(x => x?.Initialize(pageContext, canvas));
|
||||
content.HandleVisitor(x => (x as IStateResettable)?.ResetState());
|
||||
content.VisitChildren(x => x?.Initialize(pageContext, canvas));
|
||||
content.VisitChildren(x => (x as IStateResettable)?.ResetState());
|
||||
|
||||
canvas.BeginDocument();
|
||||
|
||||
@@ -128,7 +125,7 @@ namespace QuestPDF.Drawing
|
||||
|
||||
private static void ApplyCaching(Container content)
|
||||
{
|
||||
content.HandleVisitor(x =>
|
||||
content.VisitChildren(x =>
|
||||
{
|
||||
if (x is ICacheable)
|
||||
x.CreateProxy(y => new CacheProxy(y));
|
||||
@@ -139,7 +136,7 @@ namespace QuestPDF.Drawing
|
||||
{
|
||||
var debuggingState = new DebuggingState();
|
||||
|
||||
content.HandleVisitor(x =>
|
||||
content.VisitChildren(x =>
|
||||
{
|
||||
x.CreateProxy(y => new DebuggingProxy(debuggingState, y));
|
||||
});
|
||||
@@ -147,26 +144,38 @@ namespace QuestPDF.Drawing
|
||||
return debuggingState;
|
||||
}
|
||||
|
||||
internal static void ApplyDefaultTextStyle(this Element content, TextStyle documentDefaultTextStyle)
|
||||
private static void ApplyDefaultTextStyle(this Element? content, TextStyle documentDefaultTextStyle)
|
||||
{
|
||||
documentDefaultTextStyle.ApplyGlobalStyle(TextStyle.LibraryDefault);
|
||||
if (content == null)
|
||||
return;
|
||||
|
||||
content.HandleVisitor(element =>
|
||||
if (content is TextBlock textBlock)
|
||||
{
|
||||
var text = element as TextBlock;
|
||||
|
||||
if (text == null)
|
||||
return;
|
||||
|
||||
foreach (var child in text.Children)
|
||||
foreach (var textBlockItem in textBlock.Items)
|
||||
{
|
||||
if (child is TextBlockSpan textSpan)
|
||||
if (textBlockItem is TextBlockSpan textSpan)
|
||||
{
|
||||
textSpan.Style.ApplyGlobalStyle(documentDefaultTextStyle);
|
||||
|
||||
if (child is TextBlockElement textElement)
|
||||
}
|
||||
else if (textBlockItem is TextBlockElement textElement)
|
||||
{
|
||||
ApplyDefaultTextStyle(textElement.Element, documentDefaultTextStyle);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,10 @@ namespace QuestPDF.Drawing
|
||||
/// (likely due to infinite layout), the exception is thrown.
|
||||
/// </summary>
|
||||
public int DocumentLayoutExceptionThreshold { get; set; } = 250;
|
||||
|
||||
|
||||
public bool ApplyCaching { get; set; } = !System.Diagnostics.Debugger.IsAttached;
|
||||
public bool ApplyDebugging { get; set; } = System.Diagnostics.Debugger.IsAttached;
|
||||
|
||||
public static DocumentMetadata Default => new DocumentMetadata();
|
||||
}
|
||||
}
|
||||
@@ -76,7 +76,7 @@ namespace QuestPDF.Drawing.Proxy
|
||||
title += debugPointer.Highlight ? " 🌟" : string.Empty;
|
||||
}
|
||||
|
||||
if (item.SpacePlan.Type == SpacePlanType.Wrap)
|
||||
if (item.SpacePlan.Type != SpacePlanType.FullRender)
|
||||
title = "🔥 " + title;
|
||||
|
||||
builder.AppendLine(indent + title);
|
||||
@@ -109,7 +109,7 @@ namespace QuestPDF.Drawing.Proxy
|
||||
Value = x.GetValue(element)
|
||||
})
|
||||
.Where(x => !(x.Value is IElement))
|
||||
.Where(x => !(x.Value is IEnumerable<IElement>))
|
||||
.Where(x => x.Value is string || !(x.Value is IEnumerable))
|
||||
.Where(x => !(x.Value is TextStyle))
|
||||
.Select(x => $"{x.Property}: {FormatValue(x.Value)}");
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace QuestPDF.Drawing
|
||||
if (Type == SpacePlanType.Wrap)
|
||||
return Type.ToString();
|
||||
|
||||
return $"{Type} | width {Width:N2} | height {Height:N2}";
|
||||
return $"{Type} (Width: {Width:N3}, Height: {Height:N3})";
|
||||
}
|
||||
|
||||
public static implicit operator Size(SpacePlan spacePlan)
|
||||
|
||||
@@ -22,9 +22,9 @@ namespace QuestPDF.Elements
|
||||
if (Handler == null || skiaCanvas == null)
|
||||
return;
|
||||
|
||||
var currentMatrix = skiaCanvas.TotalMatrix;
|
||||
var originalMatrix = skiaCanvas.TotalMatrix;
|
||||
Handler.Invoke(skiaCanvas, availableSpace);
|
||||
skiaCanvas.SetMatrix(currentMatrix);
|
||||
skiaCanvas.SetMatrix(originalMatrix);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ namespace QuestPDF.Elements
|
||||
.Layer()
|
||||
.ShowIf(!string.IsNullOrWhiteSpace(Text))
|
||||
.AlignCenter()
|
||||
.Box()
|
||||
.MinimalBox()
|
||||
.Background(Colors.White)
|
||||
.Padding(2)
|
||||
.Text(Text, TextStyle.Default.Color(Color).FontType(Fonts.Consolas).Size(8));
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Infrastructure;
|
||||
@@ -17,12 +18,10 @@ namespace QuestPDF.Elements
|
||||
public Element ContentElement { get; set; } = Empty.Instance;
|
||||
public DecorationType Type { get; set; }
|
||||
|
||||
internal override void HandleVisitor(Action<Element?> visit)
|
||||
internal override IEnumerable<Element?> GetChildren()
|
||||
{
|
||||
DecorationElement?.HandleVisitor(visit);
|
||||
ContentElement?.HandleVisitor(visit);
|
||||
|
||||
base.HandleVisitor(visit);
|
||||
yield return DecorationElement;
|
||||
yield return ContentElement;
|
||||
}
|
||||
|
||||
internal override void CreateProxy(Func<Element, Element> create)
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class DefaultTextStyle : ContainerElement
|
||||
{
|
||||
public TextStyle TextStyle { get; set; } = TextStyle.Default;
|
||||
}
|
||||
}
|
||||
@@ -44,12 +44,11 @@ namespace QuestPDF.Elements
|
||||
ChildrenQueue = new Queue<InlinedElement>(Elements);
|
||||
}
|
||||
|
||||
internal override void HandleVisitor(Action<Element?> visit)
|
||||
internal override IEnumerable<Element?> GetChildren()
|
||||
{
|
||||
Elements.ForEach(x => x.HandleVisitor(visit));
|
||||
base.HandleVisitor(visit);
|
||||
return Elements;
|
||||
}
|
||||
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
if (!ChildrenQueue.Any())
|
||||
|
||||
@@ -15,12 +15,11 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
public List<Layer> Children { get; set; } = new List<Layer>();
|
||||
|
||||
internal override void HandleVisitor(Action<Element?> visit)
|
||||
internal override IEnumerable<Element?> GetChildren()
|
||||
{
|
||||
Children.ForEach(x => x.HandleVisitor(visit));
|
||||
base.HandleVisitor(visit);
|
||||
return Children;
|
||||
}
|
||||
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
return Children
|
||||
|
||||
@@ -3,7 +3,7 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class Box : ContainerElement
|
||||
internal class MinimalBox : ContainerElement
|
||||
{
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
@@ -40,6 +40,8 @@ namespace QuestPDF.Elements
|
||||
.PaddingTop(MarginTop)
|
||||
.PaddingBottom(MarginBottom)
|
||||
|
||||
.DefaultTextStyle(DefaultTextStyle)
|
||||
|
||||
.Decoration(decoration =>
|
||||
{
|
||||
decoration
|
||||
@@ -60,8 +62,6 @@ namespace QuestPDF.Elements
|
||||
.Element(Footer);
|
||||
});
|
||||
|
||||
(container as Element).ApplyDefaultTextStyle(DefaultTextStyle);
|
||||
|
||||
bool IsClose(float x, float y)
|
||||
{
|
||||
return Math.Abs(x - y) < Size.Epsilon;
|
||||
|
||||
+59
-61
@@ -6,9 +6,16 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal abstract class RowElement : Constrained
|
||||
internal class RowElement : Constrained
|
||||
{
|
||||
public float Size { get; protected set; }
|
||||
public float ConstantSize { get; }
|
||||
public float RelativeSize { get; }
|
||||
|
||||
public RowElement(float constantSize, float relativeSize)
|
||||
{
|
||||
ConstantSize = constantSize;
|
||||
RelativeSize = relativeSize;
|
||||
}
|
||||
|
||||
public void SetWidth(float width)
|
||||
{
|
||||
@@ -17,75 +24,75 @@ namespace QuestPDF.Elements
|
||||
}
|
||||
}
|
||||
|
||||
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 class BinaryRow : Element, ICacheable, IStateResettable
|
||||
{
|
||||
internal Element Left { get; set; }
|
||||
internal Element Right { get; set; }
|
||||
|
||||
internal override void HandleVisitor(Action<Element?> visit)
|
||||
private bool IsLeftRendered { get; set; }
|
||||
private bool IsRightRendered { get; set; }
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
Left.HandleVisitor(visit);
|
||||
Right.HandleVisitor(visit);
|
||||
|
||||
base.HandleVisitor(visit);
|
||||
IsLeftRendered = false;
|
||||
IsRightRendered = false;
|
||||
}
|
||||
|
||||
|
||||
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 (leftMeasurement.Type == SpacePlanType.PartialRender || rightMeasurement.Type == SpacePlanType.PartialRender)
|
||||
if ((!IsLeftRendered && leftMeasurement.Type == SpacePlanType.PartialRender) ||
|
||||
(!IsRightRendered && rightMeasurement.Type == SpacePlanType.PartialRender))
|
||||
return SpacePlan.PartialRender(targetSize);
|
||||
|
||||
|
||||
return SpacePlan.FullRender(targetSize);
|
||||
}
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
var leftMeasurement = Left.Measure(new Size(availableSpace.Width, availableSpace.Height));
|
||||
var leftWidth = leftMeasurement.Width;
|
||||
var leftSpace = new Size(availableSpace.Width, availableSpace.Height);
|
||||
var leftMeasurement = Left.Measure(leftSpace);
|
||||
|
||||
if (leftMeasurement.Type == SpacePlanType.FullRender)
|
||||
IsLeftRendered = true;
|
||||
|
||||
Left.Draw(new Size(leftWidth, availableSpace.Height));
|
||||
Left.Draw(new Size(leftMeasurement.Width, availableSpace.Height));
|
||||
|
||||
var rightSpace = new Size(availableSpace.Width - leftMeasurement.Width, availableSpace.Height);
|
||||
var rightMeasurement = Right.Measure(rightSpace);
|
||||
|
||||
Canvas.Translate(new Position(leftWidth, 0));
|
||||
Right.Draw(new Size(availableSpace.Width - leftWidth, availableSpace.Height));
|
||||
Canvas.Translate(new Position(-leftWidth, 0));
|
||||
if (rightMeasurement.Type == SpacePlanType.FullRender)
|
||||
IsRightRendered = true;
|
||||
|
||||
Canvas.Translate(new Position(leftMeasurement.Width, 0));
|
||||
Right.Draw(rightSpace);
|
||||
Canvas.Translate(new Position(-leftMeasurement.Width, 0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,16 +100,15 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
public float Spacing { get; set; } = 0;
|
||||
|
||||
public ICollection<RowElement> Children { get; internal set; } = new List<RowElement>();
|
||||
public ICollection<RowElement> Items { get; internal set; } = new List<RowElement>();
|
||||
private Element? RootElement { get; set; }
|
||||
|
||||
internal override void HandleVisitor(Action<Element?> visit)
|
||||
|
||||
internal override IEnumerable<Element?> GetChildren()
|
||||
{
|
||||
if (RootElement == null)
|
||||
ComposeTree();
|
||||
|
||||
RootElement.HandleVisitor(visit);
|
||||
base.HandleVisitor(visit);
|
||||
|
||||
yield return RootElement;
|
||||
}
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
@@ -121,31 +127,23 @@ namespace QuestPDF.Elements
|
||||
|
||||
private void ComposeTree()
|
||||
{
|
||||
Children = AddSpacing(Children, Spacing);
|
||||
Items = AddSpacing(Items, Spacing);
|
||||
|
||||
var elements = Children.Cast<Element>().ToArray();
|
||||
var elements = Items.Cast<Element>().ToArray();
|
||||
RootElement = BuildTree(elements);
|
||||
}
|
||||
|
||||
private void UpdateElementsWidth(float availableWidth)
|
||||
{
|
||||
var constantWidth = Children
|
||||
.Where(x => x is ConstantRowElement)
|
||||
.Cast<ConstantRowElement>()
|
||||
.Sum(x => x.Size);
|
||||
|
||||
var relativeWidth = Children
|
||||
.Where(x => x is RelativeRowElement)
|
||||
.Cast<RelativeRowElement>()
|
||||
.Sum(x => x.Size);
|
||||
var constantWidth = Items.Sum(x => x.ConstantSize);
|
||||
var relativeWidth = Items.Sum(x => x.RelativeSize);
|
||||
|
||||
var widthPerRelativeUnit = (availableWidth - constantWidth) / relativeWidth;
|
||||
var widthPerRelativeUnit = (relativeWidth > 0) ? (availableWidth - constantWidth) / relativeWidth : 0;
|
||||
|
||||
Children
|
||||
.Where(x => x is RelativeRowElement)
|
||||
.Cast<RelativeRowElement>()
|
||||
.ToList()
|
||||
.ForEach(x => x.SetWidth(x.Size * widthPerRelativeUnit));
|
||||
foreach (var row in Items)
|
||||
{
|
||||
row.SetWidth(row.ConstantSize + row.RelativeSize * widthPerRelativeUnit);
|
||||
}
|
||||
}
|
||||
|
||||
private static ICollection<RowElement> AddSpacing(ICollection<RowElement> elements, float spacing)
|
||||
@@ -154,7 +152,7 @@ namespace QuestPDF.Elements
|
||||
return elements;
|
||||
|
||||
return elements
|
||||
.SelectMany(x => new[] { new ConstantRowElement(spacing), x })
|
||||
.SelectMany(x => new[] { new RowElement(spacing, 0), x })
|
||||
.Skip(1)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
@@ -16,14 +16,12 @@ namespace QuestPDF.Elements
|
||||
|
||||
internal bool IsFirstRendered { get; set; } = false;
|
||||
|
||||
internal override void HandleVisitor(Action<Element?> visit)
|
||||
internal override IEnumerable<Element?> GetChildren()
|
||||
{
|
||||
First?.HandleVisitor(visit);
|
||||
Second?.HandleVisitor(visit);
|
||||
|
||||
base.HandleVisitor(visit);
|
||||
yield return First;
|
||||
yield return Second;
|
||||
}
|
||||
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
IsFirstRendered = false;
|
||||
@@ -97,12 +95,12 @@ namespace QuestPDF.Elements
|
||||
|
||||
internal class Stack : IComponent
|
||||
{
|
||||
public ICollection<Element> Children { get; } = new List<Element>();
|
||||
public ICollection<Element> Items { get; } = new List<Element>();
|
||||
public float Spacing { get; set; } = 0;
|
||||
|
||||
public void Compose(IContainer container)
|
||||
{
|
||||
var elements = AddSpacing(Spacing, Children);
|
||||
var elements = AddSpacing(Spacing, Items);
|
||||
|
||||
container
|
||||
.PaddingBottom(-Spacing)
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements.Table
|
||||
{
|
||||
public interface ITableCellContainer : IContainer
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
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; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
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; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
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,5 +1,6 @@
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Elements.Text.Calculation;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements.Text.Items
|
||||
@@ -10,8 +11,8 @@ namespace QuestPDF.Elements.Text.Items
|
||||
|
||||
public TextMeasurementResult? Measure(TextMeasurementRequest request)
|
||||
{
|
||||
Element.HandleVisitor(x => (x as IStateResettable)?.ResetState());
|
||||
Element.HandleVisitor(x => x.Initialize(request.PageContext, request.Canvas));
|
||||
Element.VisitChildren(x => (x as IStateResettable)?.ResetState());
|
||||
Element.VisitChildren(x => x.Initialize(request.PageContext, request.Canvas));
|
||||
|
||||
var measurement = Element.Measure(new Size(request.AvailableWidth, Size.Max.Height));
|
||||
|
||||
@@ -35,8 +36,8 @@ namespace QuestPDF.Elements.Text.Items
|
||||
|
||||
public void Draw(TextDrawingRequest request)
|
||||
{
|
||||
Element.HandleVisitor(x => (x as IStateResettable)?.ResetState());
|
||||
Element.HandleVisitor(x => x.Initialize(request.PageContext, request.Canvas));
|
||||
Element.VisitChildren(x => (x as IStateResettable)?.ResetState());
|
||||
Element.VisitChildren(x => x.Initialize(request.PageContext, request.Canvas));
|
||||
|
||||
request.Canvas.Translate(new Position(0, request.TotalAscent));
|
||||
Element.Draw(new Size(request.TextSize.Width, -request.TotalAscent));
|
||||
|
||||
@@ -11,14 +11,16 @@ namespace QuestPDF.Elements.Text
|
||||
internal class TextBlock : Element, IStateResettable
|
||||
{
|
||||
public HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
|
||||
public List<ITextBlockItem> Children { get; set; } = new List<ITextBlockItem>();
|
||||
public List<ITextBlockItem> Items { get; set; } = new List<ITextBlockItem>();
|
||||
|
||||
public Queue<ITextBlockItem> RenderingQueue { get; set; }
|
||||
public int CurrentElementIndex { get; set; }
|
||||
public string Text => string.Join(" ", Items.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>(Children);
|
||||
RenderingQueue = new Queue<ITextBlockItem>(Items);
|
||||
CurrentElementIndex = 0;
|
||||
}
|
||||
|
||||
@@ -30,7 +32,7 @@ namespace QuestPDF.Elements.Text
|
||||
var lines = DivideTextItemsIntoLines(availableSpace.Width, availableSpace.Height).ToList();
|
||||
|
||||
if (!lines.Any())
|
||||
return SpacePlan.PartialRender(Size.Zero);
|
||||
return SpacePlan.Wrap();
|
||||
|
||||
var width = lines.Max(x => x.Width);
|
||||
var height = lines.Sum(x => x.LineHeight);
|
||||
|
||||
@@ -136,14 +136,29 @@ 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 Box());
|
||||
return element.Element(new MinimalBox());
|
||||
}
|
||||
|
||||
public static IContainer MinimalBox(this IContainer element)
|
||||
{
|
||||
return element.Element(new MinimalBox());
|
||||
}
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,17 +15,19 @@ namespace QuestPDF.Fluent
|
||||
|
||||
public IContainer ConstantColumn(float width)
|
||||
{
|
||||
var element = new ConstantRowElement(width);
|
||||
|
||||
Row.Children.Add(element);
|
||||
return element;
|
||||
return Column(constantWidth: width);
|
||||
}
|
||||
|
||||
public IContainer RelativeColumn(float width = 1)
|
||||
{
|
||||
var element = new RelativeRowElement(width);
|
||||
return Column(relativeWidth: width);
|
||||
}
|
||||
|
||||
private IContainer Column(float constantWidth = 0, float relativeWidth = 0)
|
||||
{
|
||||
var element = new RowElement(constantWidth, relativeWidth);
|
||||
|
||||
Row.Children.Add(element);
|
||||
Row.Items.Add(element);
|
||||
return element;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace QuestPDF.Fluent
|
||||
public IContainer Item()
|
||||
{
|
||||
var container = new Container();
|
||||
Stack.Children.Add(container);
|
||||
Stack.Items.Add(container);
|
||||
return container;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ namespace QuestPDF.Fluent
|
||||
if (!TextBlocks.Any())
|
||||
TextBlocks.Add(new TextBlock());
|
||||
|
||||
TextBlocks.Last().Children.Add(item);
|
||||
TextBlocks.Last().Items.Add(item);
|
||||
}
|
||||
|
||||
public void Span(string? text, TextStyle? style = null)
|
||||
@@ -73,7 +73,7 @@ namespace QuestPDF.Fluent
|
||||
.Skip(1)
|
||||
.Select(x => new TextBlock
|
||||
{
|
||||
Children = new List<ITextBlockItem> { x }
|
||||
Items = new List<ITextBlockItem> { x }
|
||||
})
|
||||
.ToList()
|
||||
.ForEach(TextBlocks.Add);
|
||||
@@ -167,17 +167,14 @@ namespace QuestPDF.Fluent
|
||||
Element = container
|
||||
});
|
||||
|
||||
return container.AlignBottom().Box();
|
||||
return container.AlignBottom().MinimalBox();
|
||||
}
|
||||
|
||||
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.Stack(stack =>
|
||||
container.DefaultTextStyle(DefaultStyle).Stack(stack =>
|
||||
{
|
||||
stack.Spacing(Spacing);
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
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
|
||||
{
|
||||
@@ -41,5 +43,13 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.4f, 1009.3f);
|
||||
public static PageSize Letter => new PageSize(612.4f, 791);
|
||||
public static PageSize Legal => new PageSize(612, 1008);
|
||||
public static PageSize Letter => new PageSize(612, 792);
|
||||
|
||||
public static PageSize ARCH_A => new PageSize(649.2f, 864.7f);
|
||||
public static PageSize ARCH_B => new PageSize(864.7f, 1295.6f);
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace QuestPDF.Helpers
|
||||
{
|
||||
public static class Placeholders
|
||||
{
|
||||
public static readonly Random Random = new Random();
|
||||
public static readonly Random Random = new();
|
||||
|
||||
#region Word Cache
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Elements;
|
||||
|
||||
@@ -14,10 +15,9 @@ namespace QuestPDF.Infrastructure
|
||||
set => Child = value as Element;
|
||||
}
|
||||
|
||||
internal override void HandleVisitor(Action<Element?> visit)
|
||||
internal override IEnumerable<Element?> GetChildren()
|
||||
{
|
||||
Child?.HandleVisitor(visit);
|
||||
base.HandleVisitor(visit);
|
||||
yield return Child;
|
||||
}
|
||||
|
||||
internal override void CreateProxy(Func<Element?, Element?> create)
|
||||
|
||||
@@ -10,12 +10,12 @@ namespace QuestPDF.Infrastructure
|
||||
internal IPageContext PageContext { get; set; }
|
||||
internal ICanvas Canvas { get; set; }
|
||||
|
||||
internal virtual void HandleVisitor(Action<Element?> visit)
|
||||
internal virtual IEnumerable<Element?> GetChildren()
|
||||
{
|
||||
visit(this);
|
||||
yield break;
|
||||
}
|
||||
|
||||
internal void Initialize(IPageContext pageContext, ICanvas canvas)
|
||||
internal virtual void Initialize(IPageContext pageContext, ICanvas canvas)
|
||||
{
|
||||
PageContext = pageContext;
|
||||
Canvas = canvas;
|
||||
@@ -28,10 +28,5 @@ namespace QuestPDF.Infrastructure
|
||||
|
||||
internal abstract SpacePlan Measure(Size availableSpace);
|
||||
internal abstract void Draw(Size availableSpace);
|
||||
|
||||
protected virtual IEnumerable<string> GetDebugInformation()
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,5 +17,7 @@
|
||||
{
|
||||
return new Position(-X, -Y);
|
||||
}
|
||||
|
||||
public override string ToString() => $"(Left: {X:N3}, Top: {Y:N3})";
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
public readonly struct Size
|
||||
{
|
||||
public const float Epsilon = 0.001f;
|
||||
public const float Infinity = float.PositiveInfinity;
|
||||
|
||||
public readonly float Width;
|
||||
public readonly float Height;
|
||||
@@ -16,6 +17,6 @@
|
||||
Height = height;
|
||||
}
|
||||
|
||||
public override string ToString() => $"(W: {Width}, H: {Height})";
|
||||
public override string ToString() => $"(Width: {Width:N3}, Height: {Height:N3})";
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,10 @@
|
||||
<Authors>MarcinZiabek</Authors>
|
||||
<Company>CodeFlint</Company>
|
||||
<PackageId>QuestPDF</PackageId>
|
||||
<Version>2021.12.0-alpha0</Version>
|
||||
<Version>2022.1.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>8</LangVersion>
|
||||
<LangVersion>9</LangVersion>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<PackageIcon>Logo.png</PackageIcon>
|
||||
<PackageIconUrl>https://www.questpdf.com/images/package-logo.png</PackageIconUrl>
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
- Added support for generating documents in the XPS file format.
|
||||
- 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.
|
||||
|
||||
Reference in New Issue
Block a user