diff --git a/QuestPDF.ReportSample/PerformanceTests.cs b/QuestPDF.ReportSample/PerformanceTests.cs index 338e7fd..90f314b 100644 --- a/QuestPDF.ReportSample/PerformanceTests.cs +++ b/QuestPDF.ReportSample/PerformanceTests.cs @@ -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)); diff --git a/QuestPDF.UnitTests/TestEngine/TestPlan.cs b/QuestPDF.UnitTests/TestEngine/TestPlan.cs index 8bbe290..00136fc 100644 --- a/QuestPDF.UnitTests/TestEngine/TestPlan.cs +++ b/QuestPDF.UnitTests/TestEngine/TestPlan.cs @@ -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); diff --git a/QuestPDF/Drawing/DocumentGenerator.cs b/QuestPDF/Drawing/DocumentGenerator.cs index 0ff8bc8..f4d455c 100644 --- a/QuestPDF/Drawing/DocumentGenerator.cs +++ b/QuestPDF/Drawing/DocumentGenerator.cs @@ -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,6 +44,7 @@ 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(); @@ -58,8 +61,8 @@ namespace QuestPDF.Drawing internal static void RenderPass(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(); @@ -122,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)); @@ -133,7 +136,7 @@ namespace QuestPDF.Drawing { var debuggingState = new DebuggingState(); - content.HandleVisitor(x => + content.VisitChildren(x => { x.CreateProxy(y => new DebuggingProxy(debuggingState, y)); }); @@ -141,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); } } } \ No newline at end of file diff --git a/QuestPDF/Elements/Decoration.cs b/QuestPDF/Elements/Decoration.cs index 1a83e41..39959f4 100644 --- a/QuestPDF/Elements/Decoration.cs +++ b/QuestPDF/Elements/Decoration.cs @@ -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 visit) + internal override IEnumerable GetChildren() { - DecorationElement?.HandleVisitor(visit); - ContentElement?.HandleVisitor(visit); - - base.HandleVisitor(visit); + yield return DecorationElement; + yield return ContentElement; } internal override void CreateProxy(Func create) diff --git a/QuestPDF/Elements/DefaultTextStyle.cs b/QuestPDF/Elements/DefaultTextStyle.cs new file mode 100644 index 0000000..21f8ad2 --- /dev/null +++ b/QuestPDF/Elements/DefaultTextStyle.cs @@ -0,0 +1,10 @@ +using QuestPDF.Drawing; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Elements +{ + internal class DefaultTextStyle : ContainerElement + { + public TextStyle TextStyle { get; set; } = TextStyle.Default; + } +} \ No newline at end of file diff --git a/QuestPDF/Elements/Inlined.cs b/QuestPDF/Elements/Inlined.cs index 8afae04..9044e6e 100644 --- a/QuestPDF/Elements/Inlined.cs +++ b/QuestPDF/Elements/Inlined.cs @@ -44,12 +44,11 @@ namespace QuestPDF.Elements ChildrenQueue = new Queue(Elements); } - internal override void HandleVisitor(Action visit) + internal override IEnumerable GetChildren() { - Elements.ForEach(x => x.HandleVisitor(visit)); - base.HandleVisitor(visit); + return Elements; } - + internal override SpacePlan Measure(Size availableSpace) { if (!ChildrenQueue.Any()) diff --git a/QuestPDF/Elements/Layers.cs b/QuestPDF/Elements/Layers.cs index 24bc356..bde5ba6 100644 --- a/QuestPDF/Elements/Layers.cs +++ b/QuestPDF/Elements/Layers.cs @@ -15,12 +15,11 @@ namespace QuestPDF.Elements { public List Children { get; set; } = new List(); - internal override void HandleVisitor(Action visit) + internal override IEnumerable GetChildren() { - Children.ForEach(x => x.HandleVisitor(visit)); - base.HandleVisitor(visit); + return Children; } - + internal override SpacePlan Measure(Size availableSpace) { return Children diff --git a/QuestPDF/Elements/Row.cs b/QuestPDF/Elements/Row.cs index e077392..e3c54dc 100644 --- a/QuestPDF/Elements/Row.cs +++ b/QuestPDF/Elements/Row.cs @@ -38,14 +38,12 @@ namespace QuestPDF.Elements IsRightRendered = false; } - internal override void HandleVisitor(Action visit) + internal override IEnumerable GetChildren() { - Left.HandleVisitor(visit); - Right.HandleVisitor(visit); - - base.HandleVisitor(visit); + yield return Left; + yield return Right; } - + internal override void CreateProxy(Func create) { Left = create(Left); @@ -99,16 +97,15 @@ namespace QuestPDF.Elements { public float Spacing { get; set; } = 0; - public ICollection Children { get; internal set; } = new List(); + public ICollection Items { get; internal set; } = new List(); private Element? RootElement { get; set; } - - internal override void HandleVisitor(Action visit) + + internal override IEnumerable GetChildren() { if (RootElement == null) ComposeTree(); - - RootElement.HandleVisitor(visit); - base.HandleVisitor(visit); + + return Items; } internal override SpacePlan Measure(Size availableSpace) @@ -127,20 +124,20 @@ namespace QuestPDF.Elements private void ComposeTree() { - Children = AddSpacing(Children, Spacing); + Items = AddSpacing(Items, Spacing); - var elements = Children.Cast().ToArray(); + var elements = Items.Cast().ToArray(); RootElement = BuildTree(elements); } private void UpdateElementsWidth(float availableWidth) { - var constantWidth = Children.Sum(x => x.ConstantSize); - var relativeWidth = Children.Sum(x => x.RelativeSize); + var constantWidth = Items.Sum(x => x.ConstantSize); + var relativeWidth = Items.Sum(x => x.RelativeSize); var widthPerRelativeUnit = (relativeWidth > 0) ? (availableWidth - constantWidth) / relativeWidth : 0; - foreach (var row in Children) + foreach (var row in Items) { row.SetWidth(row.ConstantSize + row.RelativeSize * widthPerRelativeUnit); } diff --git a/QuestPDF/Elements/Stack.cs b/QuestPDF/Elements/Stack.cs index 40353e4..b190498 100644 --- a/QuestPDF/Elements/Stack.cs +++ b/QuestPDF/Elements/Stack.cs @@ -16,14 +16,12 @@ namespace QuestPDF.Elements internal bool IsFirstRendered { get; set; } = false; - internal override void HandleVisitor(Action visit) + internal override IEnumerable 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 Children { get; } = new List(); + public ICollection Items { get; } = new List(); public float Spacing { get; set; } = 0; public void Compose(IContainer container) { - var elements = AddSpacing(Spacing, Children); + var elements = AddSpacing(Spacing, Items); container .PaddingBottom(-Spacing) diff --git a/QuestPDF/Elements/Table/Table.cs b/QuestPDF/Elements/Table/Table.cs index 7cb4f39..0c555ba 100644 --- a/QuestPDF/Elements/Table/Table.cs +++ b/QuestPDF/Elements/Table/Table.cs @@ -12,51 +12,50 @@ namespace QuestPDF.Elements.Table internal class Table : Element, IStateResettable { public List Columns { get; } = new List(); - public List Children { get; } = new List(); + public List Cells { get; } = new List(); public bool ExtendLastCellsToTableBottom { get; set; } // cache for efficient cell finding // index of first array - number of row // nested array - collection of all cells starting at given row - private TableCell[][] OrderedChildren { get; set; } + private TableCell[][] OrderedCells { get; set; } private int StartingRowsCount { get; set; } private int RowsCount { get; set; } private int CurrentRow { get; set; } - internal override void HandleVisitor(Action visit) + internal override IEnumerable GetChildren() { - Children.ToList().ForEach(x => x.HandleVisitor(visit)); - base.HandleVisitor(visit); + return Cells; } - + public void ResetState() { if (StartingRowsCount == default) - StartingRowsCount = Children.Select(x => x.Row).DefaultIfEmpty(0).Max(); + StartingRowsCount = Cells.Select(x => x.Row).DefaultIfEmpty(0).Max(); if (RowsCount == default) - RowsCount = Children.Select(x => x.Row + x.RowSpan - 1).DefaultIfEmpty(0).Max(); + RowsCount = Cells.Select(x => x.Row + x.RowSpan - 1).DefaultIfEmpty(0).Max(); - if (OrderedChildren == default) + if (OrderedCells == default) { - var groups = Children + var groups = Cells .GroupBy(x => x.Row) .ToDictionary(x => x.Key, x => x.OrderBy(y => y.Column).ToArray()); - OrderedChildren = Enumerable + OrderedCells = Enumerable .Range(0, RowsCount + 1) .Select(x => groups.TryGetValue(x, out var output) ? output : Array.Empty()) .ToArray(); } - Children.ForEach(x => x.IsRendered = false); + Cells.ForEach(x => x.IsRendered = false); CurrentRow = 1; } internal override SpacePlan Measure(Size availableSpace) { - if (!Children.Any()) + if (!Cells.Any()) return SpacePlan.FullRender(Size.Zero); UpdateColumnsWidth(availableSpace.Width); @@ -149,16 +148,16 @@ namespace QuestPDF.Elements.Table { var rowBottomOffsets = new DynamicDictionary(); - var childrenToTry = Enumerable + var cellsToTry = Enumerable .Range(CurrentRow, RowsCount - CurrentRow + 1) - .SelectMany(x => OrderedChildren[x]); + .SelectMany(x => OrderedCells[x]); var currentRow = CurrentRow; var maxRenderingRow = RowsCount; var commands = new List(); - foreach (var cell in childrenToTry) + foreach (var cell in cellsToTry) { // update position of previous row if (cell.Row > currentRow) diff --git a/QuestPDF/Elements/Table/TableLayoutPlanner.cs b/QuestPDF/Elements/Table/TableLayoutPlanner.cs index d9a79d6..b5fba5b 100644 --- a/QuestPDF/Elements/Table/TableLayoutPlanner.cs +++ b/QuestPDF/Elements/Table/TableLayoutPlanner.cs @@ -9,7 +9,7 @@ namespace QuestPDF.Elements.Table { public static void PlanCellPositions(this Table table) { - PlanCellPositions(table.Columns.Count, table.Children); + PlanCellPositions(table.Columns.Count, table.Cells); } private static void PlanCellPositions(int columnsCount, ICollection cells) diff --git a/QuestPDF/Elements/Table/TableLayoutValidator.cs b/QuestPDF/Elements/Table/TableLayoutValidator.cs index 5425851..b486ab5 100644 --- a/QuestPDF/Elements/Table/TableLayoutValidator.cs +++ b/QuestPDF/Elements/Table/TableLayoutValidator.cs @@ -10,7 +10,7 @@ namespace QuestPDF.Elements.Table { public static void ValidateCellPositions(this Table table) { - ValidateCellPositions(table.Columns.Count, table.Children); + ValidateCellPositions(table.Columns.Count, table.Cells); } private static void ValidateCellPositions(int columnsCount, ICollection cells) diff --git a/QuestPDF/Elements/Text/Items/TextBlockElement.cs b/QuestPDF/Elements/Text/Items/TextBlockElement.cs index 32fbad7..feb76b9 100644 --- a/QuestPDF/Elements/Text/Items/TextBlockElement.cs +++ b/QuestPDF/Elements/Text/Items/TextBlockElement.cs @@ -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)); diff --git a/QuestPDF/Elements/Text/TextBlock.cs b/QuestPDF/Elements/Text/TextBlock.cs index 3143976..0b19928 100644 --- a/QuestPDF/Elements/Text/TextBlock.cs +++ b/QuestPDF/Elements/Text/TextBlock.cs @@ -11,16 +11,16 @@ namespace QuestPDF.Elements.Text internal class TextBlock : Element, IStateResettable { public HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left; - public List Children { get; set; } = new List(); + public List Items { get; set; } = new List(); - public string Text => string.Join(" ", Children.Where(x => x is TextBlockSpan).Cast().Select(x => x.Text)); + public string Text => string.Join(" ", Items.Where(x => x is TextBlockSpan).Cast().Select(x => x.Text)); private Queue RenderingQueue { get; set; } private int CurrentElementIndex { get; set; } public void ResetState() { - RenderingQueue = new Queue(Children); + RenderingQueue = new Queue(Items); CurrentElementIndex = 0; } diff --git a/QuestPDF/Fluent/RowExtensions.cs b/QuestPDF/Fluent/RowExtensions.cs index 55fda7f..25bba30 100644 --- a/QuestPDF/Fluent/RowExtensions.cs +++ b/QuestPDF/Fluent/RowExtensions.cs @@ -27,7 +27,7 @@ namespace QuestPDF.Fluent { var element = new RowElement(constantWidth, relativeWidth); - Row.Children.Add(element); + Row.Items.Add(element); return element; } } diff --git a/QuestPDF/Fluent/StackExtensions.cs b/QuestPDF/Fluent/StackExtensions.cs index 0d23a0c..dd14048 100644 --- a/QuestPDF/Fluent/StackExtensions.cs +++ b/QuestPDF/Fluent/StackExtensions.cs @@ -16,7 +16,7 @@ namespace QuestPDF.Fluent public IContainer Item() { var container = new Container(); - Stack.Children.Add(container); + Stack.Items.Add(container); return container; } } diff --git a/QuestPDF/Fluent/TableExtensions.cs b/QuestPDF/Fluent/TableExtensions.cs index bf33d94..8cad9ad 100644 --- a/QuestPDF/Fluent/TableExtensions.cs +++ b/QuestPDF/Fluent/TableExtensions.cs @@ -61,13 +61,13 @@ namespace QuestPDF.Fluent public ITableCellContainer Cell() { var cell = new TableCell(); - Table.Children.Add(cell); + Table.Cells.Add(cell); return cell; } internal void ApplyDefaultCellStyle() { - foreach (var cell in Table.Children) + foreach (var cell in Table.Cells) { var container = new Container(); DefaultCellStyleFunc(container).Element(cell.Child); diff --git a/QuestPDF/Fluent/TextExtensions.cs b/QuestPDF/Fluent/TextExtensions.cs index 8282e39..3fc96c2 100644 --- a/QuestPDF/Fluent/TextExtensions.cs +++ b/QuestPDF/Fluent/TextExtensions.cs @@ -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 { x } + Items = new List { x } }) .ToList() .ForEach(TextBlocks.Add); @@ -173,11 +173,8 @@ namespace QuestPDF.Fluent 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.Style.ApplyParentStyle(DefaultStyle); - container.Stack(stack => + container.DefaultTextStyle(DefaultStyle).Stack(stack => { stack.Spacing(Spacing); diff --git a/QuestPDF/Helpers/Helpers.cs b/QuestPDF/Helpers/Helpers.cs index f610ab2..48149e1 100644 --- a/QuestPDF/Helpers/Helpers.cs +++ b/QuestPDF/Helpers/Helpers.cs @@ -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 handler) + { + handler(element); + + foreach (var child in element.GetChildren().Where(x => x != null)) + VisitChildren(child, handler); + } } } \ No newline at end of file diff --git a/QuestPDF/Infrastructure/ContainerElement.cs b/QuestPDF/Infrastructure/ContainerElement.cs index aa73b8b..794c3d2 100644 --- a/QuestPDF/Infrastructure/ContainerElement.cs +++ b/QuestPDF/Infrastructure/ContainerElement.cs @@ -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 visit) + internal override IEnumerable GetChildren() { - Child?.HandleVisitor(visit); - base.HandleVisitor(visit); + yield return Child; } internal override void CreateProxy(Func create) diff --git a/QuestPDF/Infrastructure/Element.cs b/QuestPDF/Infrastructure/Element.cs index 55b56c5..a964be1 100644 --- a/QuestPDF/Infrastructure/Element.cs +++ b/QuestPDF/Infrastructure/Element.cs @@ -10,9 +10,9 @@ namespace QuestPDF.Infrastructure internal IPageContext PageContext { get; set; } internal ICanvas Canvas { get; set; } - internal virtual void HandleVisitor(Action visit) + internal virtual IEnumerable GetChildren() { - visit(this); + yield break; } internal void Initialize(IPageContext pageContext, ICanvas canvas)