Compare commits

..

16 Commits

Author SHA1 Message Date
MarcinZiabek 09e642295f Repeat content experiment 2022-10-28 23:22:11 +02:00
MarcinZiabek 65990ebf59 Added the WordWrappingStability test 2022-10-21 12:05:45 +02:00
MarcinZiabek 9d86e9efd3 DynamicImage: simplified implementation 2022-10-19 20:08:41 +02:00
Marcin Ziąbek 5cbacc7ea1 Update readme.md 2022-10-19 19:15:25 +02:00
MarcinZiabek 8214a8429d 2022.9.1 2022-10-15 23:03:25 +02:00
MarcinZiabek 389b8ce304 Inlined: improved behaviour predictability 2022-10-15 22:49:50 +02:00
MarcinZiabek e1f7cff4aa Fix: performance regression of the Table element #320 2022-10-11 23:19:13 +02:00
MarcinZiabek 6801425082 Fix: cells with RowSpan are not always displayed properly #320 2022-10-11 22:39:31 +02:00
MarcinZiabek ec31c9b063 Fix: CheckIfAllTextGlyphsAreAvailable and Fallback break hyperlinks 2022-10-11 10:31:31 +02:00
MarcinZiabek 6c8867e1b3 Added test: ReusingTheSameImageFileShouldBePossible 2022-10-06 23:20:17 +02:00
Marcin Ziąbek 20d86cbfde Update readme.md 2022-10-05 22:49:03 +02:00
MarcinZiabek 16a164e5b8 Example: achieve proportional height division 2022-10-05 01:15:36 +02:00
Marcin Ziąbek 261f087b46 Update readme.md 2022-10-04 12:30:36 +02:00
Marcin Ziąbek c876350b05 Merge pull request #354 from CollinAlpert/fix_broken_links
Fix links in README
2022-10-03 22:10:19 +02:00
Collin Alpert 58f3932241 Update readme.md 2022-10-03 15:28:48 +02:00
MarcinZiabek 9ce9ca85be Added GenerationBenchmark to test async performance 2022-09-22 22:37:15 +02:00
28 changed files with 526 additions and 110 deletions
+146
View File
@@ -0,0 +1,146 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using QuestPDF.Elements;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
namespace QuestPDF.Examples
{
public class ProcessRunningTime
{
public TimeSpan FluentTime { get; set; }
public TimeSpan GenerationTime { get; set; }
public float Size { get; set; }
}
public class GenerationBenchmark
{
public const int TestSize = 4096;
[Test]
public void BenchmarkAsync()
{
RunTest(() => Enumerable
.Range(0, TestSize)
.AsParallel() // difference
.Select(GenerateAndCollect)
.ToList());
}
[Test]
public void BenchmarkSync()
{
RunTest(() => Enumerable
.Range(0, TestSize)
.Select(GenerateAndCollect)
.ToList());
}
public void RunTest(Func<IEnumerable<ProcessRunningTime>> handler)
{
var totalFluentTime = TimeSpan.Zero;
var totalGenerationTime = TimeSpan.Zero;
var stopWatch = new Stopwatch();
stopWatch.Start();
var results = handler();
stopWatch.Stop();
foreach (var result in results)
{
totalFluentTime += result.FluentTime;
totalGenerationTime += result.GenerationTime;
}
Console.WriteLine($"Fluent: {totalFluentTime:g}");
Console.WriteLine($"Generation: {totalGenerationTime:g}");
Console.WriteLine($"Total: {stopWatch.Elapsed:g}");
}
static ProcessRunningTime GenerateAndCollect(int attemptNumber)
{
var stopwatch = new Stopwatch();
stopwatch.Start();
var container = new Container();
container
.Padding(10)
.MinimalBox()
.Border(1)
.Column(column =>
{
column.Item().Text($"Attempts {attemptNumber}");
const int numberOfRows = 100;
const int numberOfColumns = 10;
for (var y = 0; y < numberOfRows; y++)
{
column.Item().Row(row =>
{
for (var x = 0; x < numberOfColumns; x++)
{
row.RelativeItem()
.Background(Colors.Red.Lighten5)
.Padding(3)
.Background(Colors.Red.Lighten4)
.Padding(3)
.Background(Colors.Red.Lighten3)
.Padding(3)
.Background(Colors.Red.Lighten2)
.Padding(3)
.Background(Colors.Red.Lighten1)
.Padding(3)
.Background(Colors.Red.Medium)
.Padding(3)
.Background(Colors.Red.Darken1)
.Padding(3)
.Background(Colors.Red.Darken2)
.Padding(3)
.Background(Colors.Red.Darken3)
.Padding(3)
.Background(Colors.Red.Darken4)
.Height(3);
}
});
}
});
var fluentTime = stopwatch.Elapsed;
stopwatch.Reset();
stopwatch.Start();
var size = Document
.Create(x => x.Page(page => page.Content().Element(container)))
.GeneratePdf()
.Length;
var generationTime = stopwatch.Elapsed;
return new ProcessRunningTime
{
FluentTime = fluentTime,
GenerationTime = generationTime,
Size = size
};
}
}
}
+55 -1
View File
@@ -1,4 +1,5 @@
using System.IO;
using System;
using System.IO;
using NUnit.Framework;
using QuestPDF.Drawing.Exceptions;
using QuestPDF.Examples.Engine;
@@ -34,6 +35,21 @@ namespace QuestPDF.Examples
});
}
[Test]
public void DynamicImage()
{
RenderingTest
.Create()
.PageSize(450, 350)
.ProducePdf()
.ShowResults()
.Render(page =>
{
page.Padding(25)
.Image(Placeholders.Image);
});
}
[Test]
public void Exception()
{
@@ -47,5 +63,43 @@ namespace QuestPDF.Examples
.Render(page => page.Image("non_existent.png"));
});
}
[Test]
public void ReusingTheSameImageFileShouldBePossible()
{
var fileName = Path.GetTempFileName() + ".jpg";
try
{
var image = Placeholders.Image(300, 100);
using var file = File.Create(fileName);
file.Write(image);
file.Dispose();
RenderingTest
.Create()
.ProducePdf()
.PageSize(PageSizes.A4)
.ShowResults()
.Render(container =>
{
container
.Padding(20)
.Column(column =>
{
column.Spacing(20);
column.Item().Image(fileName);
column.Item().Image(fileName);
column.Item().Image(fileName);
});
});
}
finally
{
File.Delete(fileName);
}
}
}
}
@@ -0,0 +1,38 @@
using System.Linq;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
namespace QuestPDF.Examples
{
public class RepeatContentExamples
{
[Test]
public void ItemTypes()
{
RenderingTest
.Create()
.ProducePdf()
.PageSize(PageSizes.A4)
.ShowResults()
.Render(container =>
{
container
.Padding(25)
.Decoration(decoration =>
{
decoration.Before().Text("Test").FontSize(22);
decoration.Content().Column(column =>
{
column.Spacing(20);
foreach (var _ in Enumerable.Range(0, 10))
column.Item().Background(Colors.Grey.Medium).ExtendHorizontal().Height(80);
});
});
});
}
}
}
+36
View File
@@ -105,5 +105,41 @@ namespace QuestPDF.Examples
.Row(row => { });
});
}
[Test]
public void RowElementForRelativeHeightDivision()
{
RenderingTest
.Create()
.ProduceImages()
.ShowResults()
.MaxPages(100)
.PageSize(250, 400)
.Render(container =>
{
container
.Padding(25)
.AlignLeft()
.RotateRight()
.Row(row =>
{
row.Spacing(20);
row.RelativeItem(1).Element(Content);
row.RelativeItem(2).Element(Content);
row.RelativeItem(3).Element(Content);
void Content(IContainer container)
{
container
.RotateLeft()
.Border(1)
.Background(Placeholders.BackgroundColor())
.Padding(5)
.Text(Placeholders.Label());
}
});
});
}
}
}
+32
View File
@@ -657,5 +657,37 @@ namespace QuestPDF.Examples
});
});
}
[Test]
public void WordWrappingStability()
{
// instruction: check if any characters repeat when performing the word-wrapping algorithm
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.ProducePdf()
.ShowResults()
.Render(container =>
{
var text = "Lorem ipsum dolor sit amet consectetuer";
container
.Padding(20)
.Column(column =>
{
column.Spacing(10);
foreach (var width in Enumerable.Range(25, 200))
{
column
.Item()
.MaxWidth(width)
.Background(Colors.Grey.Lighten3)
.Text(text);
}
});
});
}
}
}
+49 -22
View File
@@ -64,16 +64,15 @@ namespace QuestPDF.Drawing
var container = new DocumentContainer();
document.Compose(container);
var content = container.Compose();
ApplyRepeatContent(content);
ApplyDefaultTextStyle(content, TextStyle.LibraryDefault);
ApplyContentRepeatState(content, false);
var debuggingState = Settings.EnableDebugging ? ApplyDebugging(content) : null;
if (Settings.EnableCaching)
ApplyCaching(content);
var pageContext = new PageContext();
var pageContext = new PageContext();
RenderPass(pageContext, new FreeCanvas(), content, debuggingState);
RenderPass(pageContext, canvas, content, debuggingState);
}
@@ -83,6 +82,8 @@ namespace QuestPDF.Drawing
{
content.VisitChildren(x => x?.Initialize(pageContext, canvas));
content.VisitChildren(x => (x as IStateResettable)?.ResetState());
ResetIsRenderedState(content);
canvas.BeginDocument();
@@ -162,6 +163,50 @@ namespace QuestPDF.Drawing
return debuggingState;
}
private static void ApplyContentRepeatState(Element? content, bool repeatContent)
{
if (content == null)
return;
if (content is IVisual visual)
visual.RepeatContent = repeatContent;
if (content is TextBlock textBlock)
{
foreach (var textBlockItem in textBlock.Items)
{
if (textBlockItem is TextBlockElement textElement)
{
ApplyContentRepeatState(textElement.Element, true);
}
}
return;
}
// TODO: apply RepeatState in dynamic content
//if (content is DynamicHost dynamicHost)
// dynamicHost.TextStyle = dynamicHost.TextStyle.ApplyGlobalStyle(documentDefaultTextStyle);
if (content is RepeatContentSetter repeatContentSetter)
repeatContent = repeatContentSetter.RepeatContent;
foreach (var child in content.GetChildren())
ApplyContentRepeatState(child, repeatContent);
}
private static void ResetIsRenderedState(Element? content)
{
if (content == null)
return;
if (content is IVisual visual)
visual.IsRendered = false;
foreach (var child in content.GetChildren())
ResetIsRenderedState(child);
}
internal static void ApplyDefaultTextStyle(this Element? content, TextStyle documentDefaultTextStyle)
{
@@ -194,23 +239,5 @@ namespace QuestPDF.Drawing
foreach (var child in content.GetChildren())
ApplyDefaultTextStyle(child, documentDefaultTextStyle);
}
internal static void ApplyRepeatContent(this Element? content, bool enabled = false)
{
if (content == null)
return;
if (content is RepeatContent repeatContent)
{
ApplyRepeatContent(repeatContent.Child, repeatContent.Repeat);
return;
}
foreach (var child in content.GetChildren())
ApplyRepeatContent(child, enabled);
if (!enabled)
content.CreateProxy(y => y is IContent ? new ShowOnce { Child = y } : y);
}
}
}
+13 -4
View File
@@ -7,19 +7,28 @@ namespace QuestPDF.Elements
{
public delegate void DrawOnCanvas(SKCanvas canvas, Size availableSpace);
internal class Canvas : Element, ICacheable, IContent
internal class Canvas : Element, IVisual, ICacheable
{
public bool IsRendered { get; set; }
public bool RepeatContent { get; set; }
public DrawOnCanvas Handler { get; set; }
internal override SpacePlan Measure(Size availableSpace)
{
return availableSpace.IsNegative()
? SpacePlan.Wrap()
: SpacePlan.FullRender(availableSpace);
if (availableSpace.IsNegative())
return SpacePlan.Wrap();
if (IsRendered && !RepeatContent)
return SpacePlan.FullRender(Size.Zero);
return SpacePlan.FullRender(availableSpace);
}
internal override void Draw(Size availableSpace)
{
IsRendered = true;
var skiaCanvas = (Canvas as Drawing.SkiaCanvasBase)?.Canvas;
if (Handler == null || skiaCanvas == null)
+1 -1
View File
@@ -6,7 +6,7 @@ using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class DynamicHost : Element, IStateResettable, IContent
internal class DynamicHost : Element, IStateResettable
{
private DynamicComponentProxy Child { get; }
private object InitialComponentState { get; set; }
+18 -11
View File
@@ -6,31 +6,38 @@ using SkiaSharp;
namespace QuestPDF.Elements
{
internal class DynamicImage : Element, IContent
internal class DynamicImage : Element, IVisual
{
public bool IsRendered { get; set; }
public bool RepeatContent { get; set; }
public Func<Size, byte[]>? Source { get; set; }
internal override SpacePlan Measure(Size availableSpace)
{
return availableSpace.IsNegative()
? SpacePlan.Wrap()
: SpacePlan.FullRender(availableSpace);
if (availableSpace.IsNegative())
return SpacePlan.Wrap();
if (IsRendered && !RepeatContent)
return SpacePlan.FullRender(Size.Zero);
return SpacePlan.FullRender(availableSpace);
}
internal override void Draw(Size availableSpace)
{
IsRendered = true;
if (availableSpace.Width < Size.Epsilon || availableSpace.Height < Size.Epsilon)
return;
var imageData = Source?.Invoke(availableSpace);
if (imageData == null)
return;
var imageElement = new Image
{
InternalImage = SKImage.FromEncodedData(imageData)
};
imageElement.Initialize(PageContext, Canvas);
imageElement.Draw(availableSpace);
using var image = SKImage.FromEncodedData(imageData);
Canvas.DrawImage(image, Position.Zero, availableSpace);
}
}
}
+12 -4
View File
@@ -5,8 +5,11 @@ using SkiaSharp;
namespace QuestPDF.Elements
{
internal class Image : Element, ICacheable, IContent
internal class Image : Element, IVisual, ICacheable
{
public bool IsRendered { get; set; }
public bool RepeatContent { get; set; }
public SKImage? InternalImage { get; set; }
~Image()
@@ -16,9 +19,13 @@ namespace QuestPDF.Elements
internal override SpacePlan Measure(Size availableSpace)
{
return availableSpace.IsNegative()
? SpacePlan.Wrap()
: SpacePlan.FullRender(availableSpace);
if (availableSpace.IsNegative())
return SpacePlan.Wrap();
if (IsRendered && !RepeatContent)
return SpacePlan.FullRender(Size.Zero);
return SpacePlan.FullRender(availableSpace);
}
internal override void Draw(Size availableSpace)
@@ -26,6 +33,7 @@ namespace QuestPDF.Elements
if (InternalImage == null)
return;
IsRendered = true;
Canvas.DrawImage(InternalImage, Position.Zero, availableSpace);
}
}
+1 -1
View File
@@ -212,7 +212,7 @@ namespace QuestPDF.Elements
break;
var element = queue.Peek();
var size = element.Measure(Size.Max);
var size = element.Measure(new Size(availableSize.Width, Size.Max.Height));
if (size.Type == SpacePlanType.Wrap)
break;
+14 -6
View File
@@ -15,34 +15,42 @@ namespace QuestPDF.Elements
Horizontal
}
internal class Line : Element, ILine, ICacheable, IContent
internal class Line : Element, ILine, IVisual, ICacheable
{
public bool IsRendered { get; set; }
public bool RepeatContent { get; set; }
public LineType Type { get; set; } = LineType.Vertical;
public string Color { get; set; } = Colors.Black;
public float Size { get; set; } = 1;
public float Thickness { get; set; } = 1;
internal override SpacePlan Measure(Size availableSpace)
{
if (availableSpace.IsNegative())
return SpacePlan.Wrap();
if (IsRendered && !RepeatContent)
return SpacePlan.FullRender(Size.Zero);
return Type switch
{
LineType.Vertical when availableSpace.Width + Infrastructure.Size.Epsilon >= Size => SpacePlan.FullRender(Size, 0),
LineType.Horizontal when availableSpace.Height + Infrastructure.Size.Epsilon >= Size => SpacePlan.FullRender(0, Size),
LineType.Vertical when availableSpace.Width + Infrastructure.Size.Epsilon >= Thickness => SpacePlan.FullRender(Thickness, 0),
LineType.Horizontal when availableSpace.Height + Infrastructure.Size.Epsilon >= Thickness => SpacePlan.FullRender(0, Thickness),
_ => SpacePlan.Wrap()
};
}
internal override void Draw(Size availableSpace)
{
IsRendered = true;
if (Type == LineType.Vertical)
{
Canvas.DrawRectangle(new Position(-Size/2, 0), new Size(Size, availableSpace.Height), Color);
Canvas.DrawRectangle(new Position(-Thickness/2, 0), new Size(Thickness, availableSpace.Height), Color);
}
else if (Type == LineType.Horizontal)
{
Canvas.DrawRectangle(new Position(0, -Size/2), new Size(availableSpace.Width, Size), Color);
Canvas.DrawRectangle(new Position(0, -Thickness/2), new Size(availableSpace.Width, Thickness), Color);
}
}
}
-9
View File
@@ -1,9 +0,0 @@
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class RepeatContent : ContainerElement
{
public bool Repeat { get; set; }
}
}
+9
View File
@@ -0,0 +1,9 @@
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class RepeatContentSetter : ContainerElement
{
public bool RepeatContent { get; set; }
}
}
+7 -4
View File
@@ -24,6 +24,7 @@ namespace QuestPDF.Elements.Table
// inner table: list of all cells that ends at the corresponding row
private TableCell[][] CellsCache { get; set; }
private int MaxRow { get; set; }
private int MaxRowSpan { get; set; }
internal override void Initialize(IPageContext pageContext, ICanvas canvas)
{
@@ -56,6 +57,7 @@ namespace QuestPDF.Elements.Table
if (Cells.Count == 0)
{
MaxRow = 0;
MaxRowSpan = 1;
CellsCache = Array.Empty<TableCell[]>();
return;
@@ -66,6 +68,7 @@ namespace QuestPDF.Elements.Table
.ToDictionary(x => x.Key, x => x.OrderBy(x => x.Column).ToArray());
MaxRow = groups.Max(x => x.Key);
MaxRowSpan = Cells.Max(x => x.RowSpan);
CellsCache = Enumerable
.Range(0, MaxRow + 1)
@@ -199,9 +202,9 @@ namespace QuestPDF.Elements.Table
currentRow = cell.Row;
}
// cell visibility optimizations
if (cell.Row > maxRenderingRow)
if (cell.Row > maxRenderingRow + MaxRowSpan)
break;
// calculate cell position / size
@@ -218,14 +221,14 @@ namespace QuestPDF.Elements.Table
{
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);
+16 -6
View File
@@ -121,7 +121,11 @@ namespace QuestPDF.Elements.Text
{
foreach (var textBlockItem in textBlockItems)
{
if (textBlockItem is TextBlockSpan textBlockSpan and not TextBlockPageNumber)
if (textBlockItem is TextBlockPageNumber or TextBlockElement)
{
yield return textBlockItem;
}
else if (textBlockItem is TextBlockSpan textBlockSpan)
{
if (!Settings.CheckIfAllTextGlyphsAreAvailable && textBlockSpan.Style.Fallback == null)
{
@@ -130,19 +134,25 @@ namespace QuestPDF.Elements.Text
}
var textRuns = textBlockSpan.Text.SplitWithFontFallback(textBlockSpan.Style);
foreach (var textRun in textRuns)
{
yield return new TextBlockSpan
var newElement = textBlockSpan switch
{
Text = textRun.Content,
Style = textRun.Style
TextBlockHyperlink hyperlink => new TextBlockHyperlink { Url = hyperlink.Url },
TextBlockSectionLink sectionLink => new TextBlockSectionLink { SectionName = sectionLink.SectionName },
TextBlockSpan => new TextBlockSpan()
};
newElement.Text = textRun.Content;
newElement.Style = textRun.Style;
yield return newElement;
}
}
else
{
yield return textBlockItem;
throw new NotSupportedException();
}
}
}
@@ -15,7 +15,7 @@ namespace QuestPDF.Elements.Text.Items
{
public string Text { get; set; }
public TextStyle Style { get; set; } = TextStyle.Default;
public TextShapingResult? TextShapingResult { get; set; }
private TextShapingResult? TextShapingResult { get; set; }
private Dictionary<(int startIndex, float availableWidth), TextMeasurementResult?> MeasureCache = new ();
protected virtual bool EnableTextCache => true;
+20 -4
View File
@@ -4,12 +4,16 @@ using System.Linq;
using QuestPDF.Drawing;
using QuestPDF.Elements.Text.Calculation;
using QuestPDF.Elements.Text.Items;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Text
{
internal class TextBlock : Element, IStateResettable, IContent
internal class TextBlock : Element, IVisual, IStateResettable
{
public bool IsRendered { get; set; }
public bool RepeatContent { get; set; }
public HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
public List<ITextBlockItem> Items { get; set; } = new List<ITextBlockItem>();
@@ -19,7 +23,7 @@ namespace QuestPDF.Elements.Text
private int CurrentElementIndex { get; set; }
private bool FontFallbackApplied { get; set; } = false;
public void ResetState()
{
ApplyFontFallback();
@@ -53,6 +57,12 @@ namespace QuestPDF.Elements.Text
internal override SpacePlan Measure(Size availableSpace)
{
if (availableSpace.IsNegative())
return SpacePlan.Wrap();
if (IsRendered && !RepeatContent)
return SpacePlan.FullRender(Size.Zero);
if (!RenderingQueue.Any())
return SpacePlan.FullRender(Size.Zero);
@@ -80,6 +90,9 @@ namespace QuestPDF.Elements.Text
internal override void Draw(Size availableSpace)
{
if (IsRendered && !RepeatContent)
return;
var lines = DivideTextItemsIntoLines(availableSpace.Width, availableSpace.Height).ToList();
if (!lines.Any())
@@ -136,10 +149,13 @@ namespace QuestPDF.Elements.Text
var lastElementMeasurement = lines.Last().Elements.Last().Measurement;
CurrentElementIndex = lastElementMeasurement.IsLast ? 0 : lastElementMeasurement.NextIndex;
if (!RenderingQueue.Any())
{
ResetState();
IsRendered = true;
}
float GetAlignmentOffset(float lineWidth)
{
if (Alignment == HorizontalAlignment.Left)
+4 -8
View File
@@ -12,7 +12,7 @@ namespace QuestPDF.Fluent
{
var container = new Container();
Decoration.Before = container;
return container.RepeatContent();
return container.RepeatContentWhenPaging();
}
public void Before(Action<IContainer> handler)
@@ -36,7 +36,7 @@ namespace QuestPDF.Fluent
{
var container = new Container();
Decoration.After = container;
return container.RepeatContent();
return container.RepeatContentWhenPaging();
}
public void After(Action<IContainer> handler)
@@ -49,9 +49,7 @@ namespace QuestPDF.Fluent
[Obsolete("This element has been renamed since version 2022.2. Please use the 'Before' method.")]
public IContainer Header()
{
var container = new Container();
Decoration.Before = container;
return container;
return Before();
}
[Obsolete("This element has been renamed since version 2022.2. Please use the 'Before' method.")]
@@ -63,9 +61,7 @@ namespace QuestPDF.Fluent
[Obsolete("This element has been renamed since version 2022.2. Please use the 'After' method.")]
public IContainer Footer()
{
var container = new Container();
Decoration.After = container;
return container;
return After();
}
[Obsolete("This element has been renamed since version 2022.2. Please use the 'After' method.")]
-8
View File
@@ -195,13 +195,5 @@ namespace QuestPDF.Fluent
{
return element.Element(new ScaleToFit());
}
public static IContainer RepeatContent(this IContainer element, bool enabled = true)
{
return element.Element(new RepeatContent
{
Repeat = enabled
});
}
}
}
+1 -1
View File
@@ -24,7 +24,7 @@ namespace QuestPDF.Fluent
return container;
}
public IContainer Layer() => Layer(false);
public IContainer Layer() => Layer(false).RepeatContentWhenPaging();
public IContainer PrimaryLayer() => Layer(true);
internal void Validate()
+6 -6
View File
@@ -6,11 +6,11 @@ namespace QuestPDF.Fluent
{
public static class LineExtensions
{
private static ILine Line(this IContainer element, LineType type, float size)
private static ILine Line(this IContainer element, LineType type, float thickness)
{
var line = new Line
{
Size = size,
Thickness = thickness,
Type = type
};
@@ -18,14 +18,14 @@ namespace QuestPDF.Fluent
return line;
}
public static ILine LineVertical(this IContainer element, float size, Unit unit = Unit.Point)
public static ILine LineVertical(this IContainer element, float thickness, Unit unit = Unit.Point)
{
return element.Line(LineType.Vertical, size.ToPoints(unit));
return element.Line(LineType.Vertical, thickness.ToPoints(unit));
}
public static ILine LineHorizontal(this IContainer element, float size, Unit unit = Unit.Point)
public static ILine LineHorizontal(this IContainer element, float thickness, Unit unit = Unit.Point)
{
return element.Line(LineType.Horizontal, size.ToPoints(unit));
return element.Line(LineType.Horizontal, thickness.ToPoints(unit));
}
public static void LineColor(this ILine descriptor, string value)
@@ -0,0 +1,27 @@
using System;
using QuestPDF.Elements;
using QuestPDF.Infrastructure;
namespace QuestPDF.Fluent
{
public static class RepeatContentExtensions
{
private static IContainer RepeatContent(this IContainer element, Action<RepeatContentSetter> handler)
{
var repeatContentSetter = element as RepeatContentSetter ?? new RepeatContentSetter();
handler(repeatContentSetter);
return element.Element(repeatContentSetter);
}
public static IContainer RepeatContentWhenPaging(this IContainer element)
{
return element.RepeatContent(x => x.RepeatContent = true);
}
public static IContainer DoNotRepeatContentWhenPaging(this IContainer element)
{
return element.RepeatContent(x => x.RepeatContent = false);
}
}
}
-7
View File
@@ -1,7 +0,0 @@
namespace QuestPDF.Infrastructure
{
internal interface IContent
{
}
}
+8
View File
@@ -0,0 +1,8 @@
namespace QuestPDF.Infrastructure
{
internal interface IVisual
{
public bool IsRendered { get; set; }
public bool RepeatContent { get; set; }
}
}
+1 -1
View File
@@ -3,7 +3,7 @@
<Authors>MarcinZiabek</Authors>
<Company>CodeFlint</Company>
<PackageId>QuestPDF</PackageId>
<Version>2022.9.0</Version>
<Version>2022.9.1</Version>
<PackageDescription>QuestPDF is an open-source, modern and battle-tested library that can help you with generating PDF documents by offering friendly, discoverable and predictable C# fluent API.</PackageDescription>
<PackageReleaseNotes>$([System.IO.File]::ReadAllText("$(MSBuildProjectDirectory)/Resources/ReleaseNotes.txt"))</PackageReleaseNotes>
<LangVersion>9</LangVersion>
+5
View File
@@ -4,3 +4,8 @@
- Significantly reduced memory allocation cost for TextStyle objects,
- Implemented optional checking if all font glyphs are available,
- Minor text-rendering optimizations.
2022.9.1
- Fixed: text hyperlinks do not work when the CheckIfAllTextGlyphsAreAvailable option or text fallback are used,
- Fixed: cells with RowSpan (greater than 1) are not always displayed properly,
- Improved predictability of the Inlined element when measuring its children.
+6 -5
View File
@@ -17,7 +17,7 @@ It offers a layouting engine designed with a full paging support in mind. The do
Unlike other libraries, it does not rely on the HTML-to-PDF conversion which in many cases is not reliable. Instead, it implements its own layouting engine that is optimized to cover all paging-related requirements.
## Please show the value
## Please help by giving a star
Choosing a project dependency could be difficult. We need to ensure stability and maintainability of our projects. Surveys show that GitHub stars count play an important factor when assessing library quality.
@@ -47,6 +47,7 @@ Special thanks to all companies that decided to sponsor QuestPDF development. Th
| Company | Description |
|--------------------------------------------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------|
| <img src="Resources/jetbrains-logo.svg" width="100px"> | [JetBrains](https://www.jetbrains.com/) supports this project as part of the OSS Power-Ups program. Thank you!<br/>100$ / month |
| <img src="https://avatars.githubusercontent.com/u/2712328?v=4" width="100px"> | [Mark Gould](https://github.com/markgould) supports this project. Thank you!<br/>100$ / month |
[![Sponsor project](https://img.shields.io/badge/%E2%9D%A4%EF%B8%8F%20sponsor-QuestPDF-red)](https://github.com/sponsors/QuestPDF)
@@ -62,14 +63,14 @@ Install-Package QuestPDF
dotnet add package QuestPDF
// Package reference in .csproj file
<PackageReference Include="QuestPDF" Version="2022.6.0" />
<PackageReference Include="QuestPDF" Version="2022.9.0" />
```
[![Nuget version](https://img.shields.io/badge/package%20details-QuestPDF-blue?logo=nuget)](https://www.nuget.org/packages/QuestPDF/)
## Documentation
[![Getting started tutorial]( https://img.shields.io/badge/%F0%9F%9A%80%20read-getting%20started-blue)](https://www.questpdf.com/getting-started.html)
[![Getting started tutorial]( https://img.shields.io/badge/%F0%9F%9A%80%20read-getting%20started-blue)](https://www.questpdf.com/getting-started)
A short and easy to follow tutorial showing how to design an invoice document under 200 lines of code.
@@ -77,14 +78,14 @@ A short and easy to follow tutorial showing how to design an invoice document un
A detailed description of behavior of all available components and how to use them with C# Fluent API.
[![Patterns and Practices](https://img.shields.io/badge/%E2%9C%A8%20read-patterns%20and%20practices-blue)](https://www.questpdf.com/design-patterns.html)
[![Patterns and Practices](https://img.shields.io/badge/%E2%9C%A8%20read-patterns%20and%20practices-blue)](https://www.questpdf.com/design-patterns)
Everything that may help you designing great reports and create reusable code that is easy to maintain.
## QuestPDF Previewer
The QuestPDF Previewer is a tool designed to simplify and speed up your development lifecycle. First, it shows a preview of your document. But the real magic starts with the hot-reload capability! It observes your code and updates the preview every time you change the implementation. Get real-time results without the need of code recompilation. Save time and enjoy the task!
[![Learn more](https://img.shields.io/badge/%F0%9F%93%96%20Previewer-learn%20more-blue)](https://www.questpdf.com/document-previewer.html)
[![Learn more](https://img.shields.io/badge/%F0%9F%93%96%20Previewer-learn%20more-blue)](https://www.questpdf.com/document-previewer)
<img src="https://github.com/QuestPDF/QuestPDF-Documentation/blob/main/docs/public/previewer/animation.gif?raw=true" width="100%">