diff --git a/QuestPDF.ReportSample/DataSource.cs b/QuestPDF.ReportSample/DataSource.cs index ee130c2..8b11c37 100644 --- a/QuestPDF.ReportSample/DataSource.cs +++ b/QuestPDF.ReportSample/DataSource.cs @@ -19,8 +19,8 @@ namespace QuestPDF.ReportSample HeaderFields = HeaderFields(), LogoData = Helpers.GetImage("logo.png"), - Sections = Enumerable.Range(0, 10).Select(x => GenerateSection()).ToList(), - Photos = Enumerable.Range(0, 10).Select(x => GetReportPhotos()).ToList() + Sections = Enumerable.Range(0, 8).Select(x => GenerateSection()).ToList(), + Photos = Enumerable.Range(0, 8).Select(x => GetReportPhotos()).ToList() }; List HeaderFields() diff --git a/QuestPDF.ReportSample/Layouts/SectionTemplate.cs b/QuestPDF.ReportSample/Layouts/SectionTemplate.cs index 077c533..6a983e6 100644 --- a/QuestPDF.ReportSample/Layouts/SectionTemplate.cs +++ b/QuestPDF.ReportSample/Layouts/SectionTemplate.cs @@ -38,10 +38,10 @@ namespace QuestPDF.ReportSample.Layouts frame.Text(text.Text, Typography.Normal); if (part is ReportSectionMap map) - frame.Element(container => MapElement(container, map)); + frame.Element(x => MapElement(x, map)); if (part is ReportSectionPhotos photos) - frame.Element(container => PhotosElement(container, photos)); + frame.Element(x => PhotosElement(x, photos)); }); } }); @@ -56,7 +56,7 @@ namespace QuestPDF.ReportSample.Layouts return; } - container.Stack(stack => + container.PageableStack(stack => { stack.Spacing(5); @@ -75,7 +75,7 @@ namespace QuestPDF.ReportSample.Layouts var rowCount = (int) Math.Ceiling(model.Photos.Count / 3f); - container.Padding(-2).Stack(stack => + container.Debug().Padding(-2).PageableStack(stack => { foreach (var rowId in Enumerable.Range(0, rowCount)) { diff --git a/QuestPDF.ReportSample/Tests.cs b/QuestPDF.ReportSample/Tests.cs index bb9b752..c681fc2 100644 --- a/QuestPDF.ReportSample/Tests.cs +++ b/QuestPDF.ReportSample/Tests.cs @@ -27,7 +27,7 @@ namespace QuestPDF.ReportSample public void PerformanceBenchmark() { // test size - const int testSize = 1000; + const int testSize = 250; const decimal performanceTarget = 5; // documents per second // create report models diff --git a/QuestPDF.ReportSample/Typography.cs b/QuestPDF.ReportSample/Typography.cs index 72f6ab2..7488e42 100644 --- a/QuestPDF.ReportSample/Typography.cs +++ b/QuestPDF.ReportSample/Typography.cs @@ -5,8 +5,8 @@ namespace QuestPDF.ReportSample { public static class Typography { - public static TextStyle Title => TextStyle.Default.FontType("Helvetica").Color("#000000").Size(20).Bold(); - public static TextStyle Headline => TextStyle.Default.FontType("Helvetica").Color("#047AED").Size(14); + public static TextStyle Title => TextStyle.Default.FontType("Helvetica").Color("#000000").Size(22).SemiBold(); + public static TextStyle Headline => TextStyle.Default.FontType("Helvetica").Color("#047AED").Size(14).SemiBold(); public static TextStyle Normal => TextStyle.Default.FontType("Helvetica").Color("#000000").Size(10).LineHeight(1.25f).AlignLeft(); } } \ No newline at end of file diff --git a/QuestPDF.UnitTests/ImageTests.cs b/QuestPDF.UnitTests/ImageTests.cs index 5a828f8..e0c8406 100644 --- a/QuestPDF.UnitTests/ImageTests.cs +++ b/QuestPDF.UnitTests/ImageTests.cs @@ -15,7 +15,7 @@ namespace QuestPDF.UnitTests { var image = new Image() { - Data = null + InternalImage = null }; image.Draw(It.IsAny(), Size.Zero); diff --git a/QuestPDF/Elements/AspectRatio.cs b/QuestPDF/Elements/AspectRatio.cs index 40e2323..dc86422 100644 --- a/QuestPDF/Elements/AspectRatio.cs +++ b/QuestPDF/Elements/AspectRatio.cs @@ -1,3 +1,4 @@ +using System; using QuestPDF.Drawing.SpacePlan; using QuestPDF.Infrastructure; @@ -6,17 +7,21 @@ namespace QuestPDF.Elements internal class AspectRatio : ContainerElement { public float Ratio { get; set; } = 1; + public AspectRatioOption Option { get; set; } = AspectRatioOption.FitWidth; internal override ISpacePlan Measure(Size availableSpace) { if(Child == null) return new FullRender(Size.Zero); - var size = GetSize(availableSpace); + var size = GetTargetSize(availableSpace); if (size.Height > availableSpace.Height + Size.Epsilon) return new Wrap(); + if (size.Width > availableSpace.Width + Size.Epsilon) + return new Wrap(); + return new FullRender(size); } @@ -25,7 +30,7 @@ namespace QuestPDF.Elements if (Child == null) return; - var size = GetSize(availableSpace); + var size = GetTargetSize(availableSpace); if (size.Height > availableSpace.Height) return; @@ -33,9 +38,20 @@ namespace QuestPDF.Elements Child.Draw(canvas, size); } - private Size GetSize(Size availableSpace) + private Size GetTargetSize(Size availableSpace) { - return new Size(availableSpace.Width, (int)(availableSpace.Width / Ratio)); + var spaceRatio = availableSpace.Width / availableSpace.Height; + + var fitHeight = new Size(availableSpace.Height * Ratio, availableSpace.Height) ; + var fitWidth = new Size(availableSpace.Width, availableSpace.Width / Ratio); + + return Option switch + { + AspectRatioOption.FitWidth => fitWidth, + AspectRatioOption.FitHeight => fitHeight, + AspectRatioOption.FitArea => Ratio < spaceRatio ? fitHeight : fitWidth, + _ => throw new ArgumentOutOfRangeException() + }; } } } \ No newline at end of file diff --git a/QuestPDF/Elements/Container.cs b/QuestPDF/Elements/Container.cs index 6f1731e..2a83863 100644 --- a/QuestPDF/Elements/Container.cs +++ b/QuestPDF/Elements/Container.cs @@ -6,7 +6,7 @@ namespace QuestPDF.Elements { internal Container() { - Child = new Empty(); + } } } \ No newline at end of file diff --git a/QuestPDF/Elements/Debug.cs b/QuestPDF/Elements/Debug.cs index 5f48976..d84ebe2 100644 --- a/QuestPDF/Elements/Debug.cs +++ b/QuestPDF/Elements/Debug.cs @@ -1,25 +1,35 @@ -using QuestPDF.Infrastructure; +using QuestPDF.Fluent; +using QuestPDF.Infrastructure; namespace QuestPDF.Elements { internal class Debug : ContainerElement { + private static readonly TextStyle TextStyle = TextStyle.Default.Color("#FF0000").FontType("Consolas").Size(10); + internal override void Draw(ICanvas canvas, Size availableSpace) { - var textStyle = new TextStyle - { - Color = "#FF0000", - FontType = "Consolas", - Size = 10 - }; - Child?.Draw(canvas, availableSpace); - - canvas.DrawRectangle(Position.Zero, availableSpace, "#FF0000"); - canvas.DrawRectangle(Position.Zero, availableSpace, "#FF00FF"); - - canvas.DrawText($"W: {availableSpace.Width}", new Position(5, 12), textStyle); - canvas.DrawText($"H: {availableSpace.Height}", new Position(5, 22), textStyle); + DrawBoundingBox(); + DrawDimensions(); + + void DrawBoundingBox() + { + var container = new Container(); + + container + .Border(1) + .BorderColor("#FF0000") + .Background("#33FF0000"); + + container.Draw(canvas, availableSpace); + } + + void DrawDimensions() + { + canvas.DrawText($"W: {availableSpace.Width:F1}", new Position(5, 12), TextStyle); + canvas.DrawText($"H: {availableSpace.Height:F1}", new Position(5, 22), TextStyle); + } } } } \ No newline at end of file diff --git a/QuestPDF/Elements/DynamicImage.cs b/QuestPDF/Elements/DynamicImage.cs index 0351d4e..a567e32 100644 --- a/QuestPDF/Elements/DynamicImage.cs +++ b/QuestPDF/Elements/DynamicImage.cs @@ -1,6 +1,7 @@ using System; using QuestPDF.Drawing.SpacePlan; using QuestPDF.Infrastructure; +using SkiaSharp; namespace QuestPDF.Elements { @@ -18,9 +19,14 @@ namespace QuestPDF.Elements internal override void Draw(ICanvas canvas, Size availableSpace) { - var imageElement = new Image() + var imageData = Source?.Invoke(availableSpace); + + if (imageData == null) + return; + + var imageElement = new Image { - Data = Source?.Invoke(availableSpace) + InternalImage = SKImage.FromEncodedData(imageData) }; imageElement.Draw(canvas, availableSpace); diff --git a/QuestPDF/Elements/Image.cs b/QuestPDF/Elements/Image.cs index 31e2529..8d58e23 100644 --- a/QuestPDF/Elements/Image.cs +++ b/QuestPDF/Elements/Image.cs @@ -6,53 +6,24 @@ namespace QuestPDF.Elements { internal class Image : Element { - public byte[]? Data { get; set; } - private SKImage? InternalImage { get; set; } + public SKImage? InternalImage { get; set; } ~Image() { InternalImage?.Dispose(); } - - private void Initialize() - { - if (Data != null) - InternalImage ??= SKImage.FromEncodedData(Data); - } internal override ISpacePlan Measure(Size availableSpace) { - Initialize(); - - if (InternalImage == null) - return new FullRender(Size.Zero); - - if (availableSpace.Width < 0 || availableSpace.Height < 0) - return new Wrap(); - - var size = GetTargetSize(availableSpace); - return new FullRender(size); + return new FullRender(availableSpace); } internal override void Draw(ICanvas canvas, Size availableSpace) { - Initialize(); - if (InternalImage == null) return; - var size = GetTargetSize(availableSpace); - canvas.DrawImage(InternalImage, Position.Zero, size); - } - - private Size GetTargetSize(Size availableSpace) - { - var imageRatio = InternalImage.Width / (float)InternalImage.Height; - var spaceRatio = availableSpace.Width / (float) availableSpace.Height; - - return imageRatio < spaceRatio - ? new Size((int)(availableSpace.Height * imageRatio), availableSpace.Height) - : new Size(availableSpace.Width, (int)(availableSpace.Width / imageRatio)); + canvas.DrawImage(InternalImage, Position.Zero, availableSpace); } } } \ No newline at end of file diff --git a/QuestPDF/Elements/Section.cs b/QuestPDF/Elements/Section.cs index 9ed70ba..40ee47b 100644 --- a/QuestPDF/Elements/Section.cs +++ b/QuestPDF/Elements/Section.cs @@ -6,8 +6,8 @@ namespace QuestPDF.Elements { internal class Section : Element { - public Element? Header { get; set; } - public Element? Content { get; set; } + public ContainerElement? Header { get; set; } + public ContainerElement? Content { get; set; } internal override ISpacePlan Measure(Size availableSpace) { diff --git a/QuestPDF/Fluent/ElementExtensions.cs b/QuestPDF/Fluent/ElementExtensions.cs index 0cc0b6e..a2153ef 100644 --- a/QuestPDF/Fluent/ElementExtensions.cs +++ b/QuestPDF/Fluent/ElementExtensions.cs @@ -4,6 +4,7 @@ using QuestPDF.Drawing; using QuestPDF.Drawing.Exceptions; using QuestPDF.Elements; using QuestPDF.Infrastructure; +using SkiaSharp; namespace QuestPDF.Fluent { @@ -33,21 +34,51 @@ namespace QuestPDF.Fluent return child; } - public static void Element(this IContainer element, Action handler) + public static void Element(this TParent parent, Action handler) where TParent : IContainer { - var container = new Container(); - element.Element(container); - handler?.Invoke(container); - } - - public static void Image(this IContainer element, byte[] data) - { - element.Element(new Image - { - Data = data - }); + handler(parent.Container()); } + public static IContainer Element(this TParent parent, Func handler) where TParent : IContainer + { + return handler(parent.Container()).Container(); + } + + public static IContainer Debug(this IContainer parent) + { + return parent.Element(new Debug()); + } + + public static void Image(this IContainer parent, byte[] data, ImageScaling scaling = ImageScaling.FitWidth) + { + if (data == null) + return; + + var image = SKImage.FromEncodedData(data); + var aspectRatio = image.Width / (float)image.Height; + + var imageElement = new Image + { + InternalImage = image + }; + + if (scaling != ImageScaling.Resize) + parent = parent.AspectRatio(aspectRatio, Map(scaling)); + + parent.Element(imageElement); + + static AspectRatioOption Map(ImageScaling scaling) + { + return scaling switch + { + ImageScaling.FitWidth => AspectRatioOption.FitWidth, + ImageScaling.FitHeight => AspectRatioOption.FitHeight, + ImageScaling.FitArea => AspectRatioOption.FitArea, + _ => throw new ArgumentOutOfRangeException() + }; + } + } + public static void DynamicImage(this IContainer element, Func imageSource) { element.Element(new DynamicImage @@ -65,11 +96,12 @@ namespace QuestPDF.Fluent }); } - public static IContainer AspectRatio(this IContainer element, float ratio) + public static IContainer AspectRatio(this IContainer element, float ratio, AspectRatioOption option = AspectRatioOption.FitWidth) { return element.Element(new AspectRatio { - Ratio = ratio + Ratio = ratio, + Option = option }); } diff --git a/QuestPDF/Infrastructure/AspectRatioOption.cs b/QuestPDF/Infrastructure/AspectRatioOption.cs new file mode 100644 index 0000000..0a4b86a --- /dev/null +++ b/QuestPDF/Infrastructure/AspectRatioOption.cs @@ -0,0 +1,9 @@ +namespace QuestPDF.Infrastructure +{ + public enum AspectRatioOption + { + FitWidth, + FitHeight, + FitArea + } +} \ No newline at end of file diff --git a/QuestPDF/Infrastructure/ImageScaling.cs b/QuestPDF/Infrastructure/ImageScaling.cs new file mode 100644 index 0000000..2889237 --- /dev/null +++ b/QuestPDF/Infrastructure/ImageScaling.cs @@ -0,0 +1,10 @@ +namespace QuestPDF.Infrastructure +{ + public enum ImageScaling + { + FitWidth, + FitHeight, + FitArea, + Resize + } +} \ No newline at end of file