Browse Source

Reduced allocations: Text Fluent API

Marcin Ziąbek 2 years ago
parent
commit
8c612c5706
1 changed files with 70 additions and 28 deletions
  1. 70 28
      Source/QuestPDF/Fluent/TextExtensions.cs

+ 70 - 28
Source/QuestPDF/Fluent/TextExtensions.cs

@@ -12,18 +12,46 @@ namespace QuestPDF.Fluent
 {
     public class TextSpanDescriptor
     {
-        internal TextStyle TextStyle = TextStyle.Default;
-        internal Action<TextStyle> AssignTextStyle { get; }
+        // optimization: only of fields below has value, where TextBlockSpan is more frequent
+        private TextBlockSpan? TextBlockSpan;
+        private ICollection<TextBlockSpan>? TextBlockSpans;
 
-        internal TextSpanDescriptor(Action<TextStyle> assignTextStyle)
+        internal TextSpanDescriptor(TextBlockSpan textBlockSpan)
         {
-            AssignTextStyle = assignTextStyle;
+            TextBlockSpan = textBlockSpan;
+        }
+        
+        internal TextSpanDescriptor(ICollection<TextBlockSpan> textBlockSpans)
+        {
+            TextBlockSpans = textBlockSpans;
         }
 
+        internal void MutateTextStyle<T>(Func<TextStyle, T, TextStyle> handler, T argument)
+        {
+            if (TextBlockSpan != null)
+            {
+                TextBlockSpan.Style = handler(TextBlockSpan.Style, argument);
+                return;
+            }
+            
+            foreach (var textBlockSpan in TextBlockSpans)
+            {
+                textBlockSpan.Style = handler(textBlockSpan.Style, argument);
+            }
+        }
+        
         internal void MutateTextStyle(Func<TextStyle, TextStyle> handler)
         {
-            TextStyle = handler(TextStyle);
-            AssignTextStyle(TextStyle);
+            if (TextBlockSpan != null)
+            {
+                TextBlockSpan.Style = handler(TextBlockSpan.Style);
+                return;
+            }
+            
+            foreach (var textBlockSpan in TextBlockSpans)
+            {
+                textBlockSpan.Style = handler(textBlockSpan.Style);
+            }
         }
     }
 
@@ -39,7 +67,7 @@ namespace QuestPDF.Fluent
     {
         internal Action<PageNumberFormatter> AssignFormatFunction { get; }
         
-        internal TextPageNumberDescriptor(Action<TextStyle> assignTextStyle, Action<PageNumberFormatter> assignFormatFunction) : base(assignTextStyle)
+        internal TextPageNumberDescriptor(TextBlockSpan textBlockSpan, Action<PageNumberFormatter> assignFormatFunction) : base(textBlockSpan)
         {
             AssignFormatFunction = assignFormatFunction;
             AssignFormatFunction(x => x?.ToString());
@@ -147,29 +175,37 @@ namespace QuestPDF.Fluent
         public TextSpanDescriptor Span(string? text)
         {
             if (text == null)
-                return new TextSpanDescriptor(_ => { });
- 
+                return new TextSpanDescriptor(Array.Empty<TextBlockSpan>());
+
+            if (text.Contains('\r'))
+                text = text.Replace("\r", string.Empty);
+            
+            if (!text.Contains('\n'))
+            {
+                var textBlockSpan = new TextBlockSpan { Text = text };
+                AddItemToLastTextBlock(textBlockSpan);
+                return new TextSpanDescriptor(textBlockSpan);
+            }
+            
             var items = text
-                .Replace("\r", string.Empty)
                 .Split(new[] { '\n' }, StringSplitOptions.None)
                 .Select(x => new TextBlockSpan
                 {
                     Text = x
                 })
-                .ToList();
+                .ToArray();
 
             AddItemToLastTextBlock(items.First());
-
-            items
-                .Skip(1)
-                .Select(x => new TextBlock
+            
+            foreach (var textBlockSpan in items.Skip(1))
+            {
+                TextBlocks.Add(new TextBlock
                 {   
-                    Items = new List<ITextBlockItem> { x }
-                })
-                .ToList()
-                .ForEach(TextBlocks.Add);
+                    Items = new List<ITextBlockItem> { textBlockSpan }
+                });
+            }
 
-            return new TextSpanDescriptor(x => items.ForEach(y => y.Style = x));
+            return new TextSpanDescriptor(items);
         }
 
         /// <summary>
@@ -196,7 +232,7 @@ namespace QuestPDF.Fluent
             var textBlockItem = new TextBlockPageNumber();
             AddItemToLastTextBlock(textBlockItem);
             
-            return new TextPageNumberDescriptor(x => textBlockItem.Style = x, x => textBlockItem.Source = context => x(pageNumber(context)));
+            return new TextPageNumberDescriptor(textBlockItem, x => textBlockItem.Source = context => x(pageNumber(context)));
         }
 
         /// <summary>
@@ -280,7 +316,7 @@ namespace QuestPDF.Fluent
                 throw new ArgumentException("Section name cannot be null or empty", nameof(sectionName));
 
             if (IsNullOrEmpty(text))
-                return new TextSpanDescriptor(_ => { });
+                return new TextSpanDescriptor(Array.Empty<TextBlockSpan>());
 
             var textBlockItem = new TextBlockSectionLink
             {
@@ -289,7 +325,7 @@ namespace QuestPDF.Fluent
             };
 
             AddItemToLastTextBlock(textBlockItem);
-            return new TextSpanDescriptor(x => textBlockItem.Style = x);
+            return new TextSpanDescriptor(textBlockItem);
         }
         
         [Obsolete("This element has been renamed since version 2022.3. Please use the SectionLink method.")]
@@ -309,7 +345,7 @@ namespace QuestPDF.Fluent
                 throw new ArgumentException("Url cannot be null or empty", nameof(url));
 
             if (IsNullOrEmpty(text))
-                return new TextSpanDescriptor(_ => { });
+                return new TextSpanDescriptor(Array.Empty<TextBlockSpan>());
             
             var textBlockItem = new TextBlockHyperlink
             {
@@ -318,7 +354,7 @@ namespace QuestPDF.Fluent
             };
 
             AddItemToLastTextBlock(textBlockItem);
-            return new TextSpanDescriptor(x => textBlockItem.Style = x);
+            return new TextSpanDescriptor(textBlockItem);
         }
         
         [Obsolete("This element has been renamed since version 2022.3. Please use the Hyperlink method.")]
@@ -422,9 +458,15 @@ namespace QuestPDF.Fluent
         /// <include file='../Resources/Documentation.xml' path='documentation/doc[@for="text.returns.spanDescriptor"]/*' />
         public static TextSpanDescriptor Text(this IContainer element, string? text)
         {
-            var descriptor = (TextSpanDescriptor) null!;
-            element.Text(x => descriptor = x.Span(text));
-            return descriptor;
+            var textDescriptor = new TextDescriptor();
+            
+            if (element is Alignment alignment)
+                textDescriptor.Alignment = alignment.Horizontal;
+            
+            var span = textDescriptor.Span(text);
+            textDescriptor.Compose(element);
+
+            return span;
         }
     }
 }