From 479915b68f7a91dbb80c82b92a4f988b990ecf7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Zi=C4=85bek?= Date: Mon, 11 Oct 2021 01:48:12 +0200 Subject: [PATCH] Implemented the Inlined element --- QuestPDF.Examples/InlinedExamples.cs | 49 +++++++ QuestPDF/Elements/Grid.cs | 9 +- QuestPDF/Elements/Inlined.cs | 195 +++++++++++++++++++++++++++ QuestPDF/Fluent/InlinedExtensions.cs | 81 +++++++++++ 4 files changed, 329 insertions(+), 5 deletions(-) create mode 100644 QuestPDF.Examples/InlinedExamples.cs create mode 100644 QuestPDF/Elements/Inlined.cs create mode 100644 QuestPDF/Fluent/InlinedExtensions.cs diff --git a/QuestPDF.Examples/InlinedExamples.cs b/QuestPDF.Examples/InlinedExamples.cs new file mode 100644 index 0000000..a9e3a63 --- /dev/null +++ b/QuestPDF.Examples/InlinedExamples.cs @@ -0,0 +1,49 @@ +using System; +using System.Linq; +using NUnit.Framework; +using QuestPDF.Examples.Engine; +using QuestPDF.Fluent; +using QuestPDF.Helpers; + +namespace QuestPDF.Examples +{ + public class InlinedExamples + { + [Test] + public void Inlined() + { + RenderingTest + .Create() + .PageSize(800, 575) + .FileName() + .ProduceImages() + .ShowResults() + .Render(container => + { + container + .Padding(25) + .Border(1) + .Background(Colors.Grey.Lighten2) + .Inlined(inlined => + { + inlined.Spacing(25); + + inlined.AlignCenter(); + inlined.BaselineMiddle(); + + var random = new Random(); + + foreach (var _ in Enumerable.Range(0, 50)) + { + inlined + .Item() + .Border(1) + .Width(random.Next(1, 5) * 25) + .Height(random.Next(1, 5) * 25) + .Background(Placeholders.BackgroundColor()); + } + }); + }); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Elements/Grid.cs b/QuestPDF/Elements/Grid.cs index 4649ac4..7014bf6 100644 --- a/QuestPDF/Elements/Grid.cs +++ b/QuestPDF/Elements/Grid.cs @@ -2,7 +2,6 @@ using System.Linq; using QuestPDF.Fluent; using QuestPDF.Infrastructure; -using static QuestPDF.Infrastructure.HorizontalAlignment; namespace QuestPDF.Elements { @@ -20,7 +19,7 @@ namespace QuestPDF.Elements public Queue ChildrenQueue { get; set; } = new Queue(); public int ColumnsCount { get; set; } = DefaultColumnsCount; - public HorizontalAlignment Alignment { get; set; } = Left; + public HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left; public float VerticalSpacing { get; set; } = 0; public float HorizontalSpacing { get; set; } = 0; @@ -62,15 +61,15 @@ namespace QuestPDF.Elements var emptySpace = ColumnsCount - columnsWidth; var hasEmptySpace = emptySpace >= Size.Epsilon; - if (Alignment == Center) + if (Alignment == HorizontalAlignment.Center) emptySpace /= 2; - if (hasEmptySpace && Alignment != Left) + if (hasEmptySpace && Alignment != HorizontalAlignment.Left) row.RelativeColumn(emptySpace); elements.ForEach(x => row.RelativeColumn(x.Columns).Element(x.Child)); - if (hasEmptySpace && Alignment != Right) + if (hasEmptySpace && Alignment != HorizontalAlignment.Right) row.RelativeColumn(emptySpace); } } diff --git a/QuestPDF/Elements/Inlined.cs b/QuestPDF/Elements/Inlined.cs new file mode 100644 index 0000000..0ab3352 --- /dev/null +++ b/QuestPDF/Elements/Inlined.cs @@ -0,0 +1,195 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using QuestPDF.Drawing.SpacePlan; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Elements +{ + internal class InlinedElement : Container + { + public ISpacePlan? Size { get; set; } + } + + internal class Inlined : Element, IStateResettable + { + public List Elements { get; internal set; } = new List(); + private Queue ChildrenQueue { get; set; } + + internal HorizontalAlignment HorizontalAlignment { get; set; } + internal VerticalAlignment BaselineAlignment { get; set; } + + public void ResetState() + { + Elements.ForEach(x => x.Size ??= x.Measure(Size.Max)); + ChildrenQueue = new Queue(Elements); + } + + internal override void HandleVisitor(Action visit) + { + Elements.ForEach(x => x.HandleVisitor(visit)); + base.HandleVisitor(visit); + } + + internal override ISpacePlan Measure(Size availableSpace) + { + if (!ChildrenQueue.Any()) + return new FullRender(Size.Zero); + + var lines = Compose(availableSpace); + + if (!lines.Any()) + return new Wrap(); + + var lineSizes = lines.Select(GetLineSize).ToList(); + + var width = lineSizes.Max(x => x.Width); + var height = lineSizes.Sum(x => x.Height); + var targetSize = new Size(width, height); + + var isPartiallyRendered = lines.Sum(x => x.Count) != ChildrenQueue.Count; + + if (isPartiallyRendered) + return new PartialRender(targetSize); + + return new FullRender(targetSize); + } + + internal override void Draw(Size availableSpace) + { + var lines = Compose(availableSpace); + var topOffset = 0f; + + foreach (var line in lines) + { + var height = line.Select(x => x.Size as Size).Where(x => x != null).Max(x => x.Height); + DrawLine(line); + + topOffset += height; + Canvas.Translate(new Position(0, height)); + } + + Canvas.Translate(new Position(0, -topOffset)); + lines.SelectMany(x => x).ToList().ForEach(x => ChildrenQueue.Dequeue()); + + void DrawLine(ICollection elements) + { + var lineSize = GetLineSize(elements); + + var leftOffset = AlignOffset(); + Canvas.Translate(new Position(leftOffset, 0)); + + foreach (var element in elements) + { + var size = element.Size as Size; + var baselineOffset = BaselineOffset(size, lineSize.Height); + + Canvas.Translate(new Position(0, baselineOffset)); + element.Draw(size); + Canvas.Translate(new Position(0, -baselineOffset)); + + leftOffset += size.Width; + Canvas.Translate(new Position(size.Width, 0)); + } + + Canvas.Translate(new Position(-leftOffset, 0)); + + float AlignOffset() + { + if (HorizontalAlignment == HorizontalAlignment.Left) + return 0; + + var difference = availableSpace.Width - lineSize.Width; + + if (HorizontalAlignment == HorizontalAlignment.Center) + return difference / 2; + + return difference; + } + + float BaselineOffset(Size elementSize, float lineHeight) + { + if (BaselineAlignment == VerticalAlignment.Top) + return 0; + + var difference = lineHeight - elementSize.Height; + + if (BaselineAlignment == VerticalAlignment.Middle) + return difference / 2; + + return difference; + } + } + } + + Size GetLineSize(ICollection elements) + { + var sizes = elements + .Select(x => x.Size as Size) + .Where(x => x != null) + .ToList(); + + var width = sizes.Sum(x => x.Width); + var height = sizes.Max(x => x.Height); + + return new Size(width, height); + } + + // list of lines, each line is a list of elements + private ICollection> Compose(Size availableSize) + { + var queue = new Queue(ChildrenQueue); + var result = new List>(); + + var topOffset = 0f; + + while (true) + { + var line = GetNextLine(); + + if (!line.Any()) + break; + + var height = line + .Select(x => x.Size as Size) + .Where(x => x != null) + .Max(x => x.Height); + + if (topOffset + height > availableSize.Height) + break; + + topOffset += height; + result.Add(line); + } + + return result; + + ICollection GetNextLine() + { + var result = new List(); + var leftOffset = 0f; + + while (true) + { + if (!queue.Any()) + break; + + var element = queue.Peek(); + var size = element.Size as Size; + + if (size == null) + break; + + if (leftOffset + size.Width > availableSize.Width) + break; + + queue.Dequeue(); + leftOffset += size.Width; + result.Add(element); + } + + return result; + } + } + } +} \ No newline at end of file diff --git a/QuestPDF/Fluent/InlinedExtensions.cs b/QuestPDF/Fluent/InlinedExtensions.cs new file mode 100644 index 0000000..7075826 --- /dev/null +++ b/QuestPDF/Fluent/InlinedExtensions.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using QuestPDF.Elements; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Fluent +{ + public class InlinedDescriptor + { + private ICollection Children = new List(); + private float VerticalSpacingValue { get; set; } + private VerticalAlignment BaselineAlignmentValue { get; set; } + private float HorizontalSpacingValue { get; set; } + private HorizontalAlignment HorizontalAlignmentValue { get; set; } + + public void Spacing(float value) + { + VerticalSpacing(value); + HorizontalSpacing(value); + } + + public void VerticalSpacing(float value) => VerticalSpacingValue = value; + public void HorizontalSpacing(float value) => HorizontalSpacingValue = value; + + public void BaselineTop() => BaselineAlignmentValue = VerticalAlignment.Top; + public void BaselineMiddle() => BaselineAlignmentValue = VerticalAlignment.Middle; + public void BaselineBottom() => BaselineAlignmentValue = VerticalAlignment.Bottom; + + public void AlignLeft() => HorizontalAlignmentValue = HorizontalAlignment.Left; + public void AlignCenter() => HorizontalAlignmentValue = HorizontalAlignment.Center; + public void AlignRight() => HorizontalAlignmentValue = HorizontalAlignment.Right; + + public IContainer Item() + { + var container = new Container(); + Children.Add(container); + return container; + } + + internal Element Compose() + { + var elements = Children + .Select(x => new InlinedElement + { + Child = new Padding + { + Left = HorizontalSpacingValue, + Top = VerticalSpacingValue, + Child = x + } + }) + .ToList(); + + return new Padding + { + Left = -HorizontalSpacingValue, + Top = -VerticalSpacingValue, + + Child = new Inlined + { + Elements = elements, + + HorizontalAlignment = HorizontalAlignmentValue, + BaselineAlignment = BaselineAlignmentValue + } + }; + } + } + + public static class InlinedExtensions + { + public static void Inlined(this IContainer element, Action handler) + { + var descriptor = new InlinedDescriptor(); + handler(descriptor); + + element.Element(descriptor.Compose()); + } + } +} \ No newline at end of file