Browse Source

Merge branch 'main' into feat-layout-testing

Marcin Ziąbek 2 years ago
parent
commit
1bcb92221f

+ 2 - 2
README.md

@@ -17,7 +17,7 @@
 ### QuestPDF is a modern open-source .NET library for PDF document generation. Offering comprehensive layout engine powered by concise and discoverable C# Fluent API.
 
 <img src="https://github.com/QuestPDF/QuestPDF-Documentation/blob/main/docs/public/previewer/animation.gif?raw=true" width="100%">
- 
+
 <table>
 <tr>
     <td>👨‍💻</td>
@@ -56,7 +56,7 @@ Choosing a project dependency could be difficult. We need to ensure stability an
 
 ⭐ Please give this repository a star. It takes seconds and help thousands of developers! ⭐
 
-<img src="https://github.com/QuestPDF/QuestPDF/assets/9263853/cd3fead8-975b-4edf-b387-a84ccb6260b5" width="700" />
+<img src="https://github.com/QuestPDF/QuestPDF/assets/9263853/1d8fe760-c0e0-4c6d-b7c6-46369f786841" width="700" />
 
 ## Please share with the community
 

+ 1 - 1
Source/QuestPDF/Elements/DynamicImage.cs

@@ -10,7 +10,7 @@ namespace QuestPDF.Elements
     /// </summary>
     /// <param name="size">Desired resolution of the image in pixels.</param>
     /// <returns>An image in PNG, JPEG, or WEBP image format returned as byte array.</returns>
-    public delegate byte[] GenerateDynamicImageDelegate(ImageSize size);
+    public delegate byte[]? GenerateDynamicImageDelegate(ImageSize size);
 
     internal sealed class DynamicImage : Element
     {

+ 23 - 9
Source/QuestPDF/Fluent/AlignmentExtensions.cs

@@ -6,11 +6,12 @@ namespace QuestPDF.Fluent
 {
     public static class AlignmentExtensions
     {
-        private static IContainer Alignment(this IContainer element, Action<Alignment> handler)
+        #region Horizontal
+        
+        private static IContainer AlignHorizontal(this IContainer element, HorizontalAlignment horizontalAlignment)
         {
             var alignment = element as Alignment ?? new Alignment();
-            handler(alignment);
-            
+            alignment.Horizontal = horizontalAlignment;
             return element.Element(alignment);
         }
         
@@ -20,7 +21,7 @@ namespace QuestPDF.Fluent
         /// </summary>
         public static IContainer AlignLeft(this IContainer element)
         {
-            return element.Alignment(x => x.Horizontal = HorizontalAlignment.Left);
+            return element.AlignHorizontal(HorizontalAlignment.Left);
         }
         
         /// <summary>
@@ -29,7 +30,7 @@ namespace QuestPDF.Fluent
         /// </summary>
         public static IContainer AlignCenter(this IContainer element)
         {
-            return element.Alignment(x => x.Horizontal = HorizontalAlignment.Center);
+            return element.AlignHorizontal(HorizontalAlignment.Center);
         }
         
         /// <summary>
@@ -38,7 +39,18 @@ namespace QuestPDF.Fluent
         /// </summary>
         public static IContainer AlignRight(this IContainer element)
         {
-            return element.Alignment(x => x.Horizontal = HorizontalAlignment.Right);
+            return element.AlignHorizontal(HorizontalAlignment.Right);
+        }
+        
+        #endregion
+        
+        #region Vertical
+        
+        private static IContainer AlignVertical(this IContainer element, VerticalAlignment verticalAlignment)
+        {
+            var alignment = element as Alignment ?? new Alignment();
+            alignment.Vertical = verticalAlignment;
+            return element.Element(alignment);
         }
         
         /// <summary>
@@ -47,7 +59,7 @@ namespace QuestPDF.Fluent
         /// </summary>
         public static IContainer AlignTop(this IContainer element)
         {
-            return element.Alignment(x => x.Vertical = VerticalAlignment.Top);
+            return element.AlignVertical(VerticalAlignment.Top);
         }
         
         /// <summary>
@@ -56,7 +68,7 @@ namespace QuestPDF.Fluent
         /// </summary>
         public static IContainer AlignMiddle(this IContainer element)
         {
-            return element.Alignment(x => x.Vertical = VerticalAlignment.Middle);
+            return element.AlignVertical(VerticalAlignment.Middle);
         }
         
         /// <summary>
@@ -65,7 +77,9 @@ namespace QuestPDF.Fluent
         /// </summary>
         public static IContainer AlignBottom(this IContainer element)
         {
-            return element.Alignment(x => x.Vertical = VerticalAlignment.Bottom);
+            return element.AlignVertical(VerticalAlignment.Bottom);
         }
+        
+        #endregion
     }
 }

+ 25 - 17
Source/QuestPDF/Fluent/BorderExtensions.cs

@@ -7,11 +7,15 @@ namespace QuestPDF.Fluent
 {
     public static class BorderExtensions
     {
-        private static IContainer Border(this IContainer element, Action<Border> handler)
+        private static IContainer Border(this IContainer element, float top = 0, float bottom = 0, float left = 0, float right = 0)
         {
             var border = element as Border ?? new Border();
-            handler(border);
-            
+
+            border.Top += top;
+            border.Bottom += bottom;
+            border.Left += left;
+            border.Right += right;
+
             return element.Element(border);
         }
         
@@ -21,9 +25,8 @@ namespace QuestPDF.Fluent
         /// </summary>
         public static IContainer Border(this IContainer element, float value, Unit unit = Unit.Point)
         {
-            return element
-                .BorderHorizontal(value, unit)
-                .BorderVertical(value, unit);
+            value = value.ToPoints(unit);
+            return element.Border(top: value, bottom: value, left: value, right: value);
         }
         
         /// <summary>
@@ -32,9 +35,8 @@ namespace QuestPDF.Fluent
         /// </summary>
         public static IContainer BorderVertical(this IContainer element, float value, Unit unit = Unit.Point)
         {
-            return element
-                .BorderLeft(value, unit)
-                .BorderRight(value, unit);
+            value = value.ToPoints(unit);
+            return element.Border(left: value, right: value);
         }
         
         /// <summary>
@@ -43,9 +45,8 @@ namespace QuestPDF.Fluent
         /// </summary>
         public static IContainer BorderHorizontal(this IContainer element, float value, Unit unit = Unit.Point)
         {
-            return element
-                .BorderTop(value, unit)
-                .BorderBottom(value, unit);
+            value = value.ToPoints(unit);
+            return element.Border(top: value, bottom: value);
         }
         
         /// <summary>
@@ -54,7 +55,8 @@ namespace QuestPDF.Fluent
         /// </summary>
         public static IContainer BorderLeft(this IContainer element, float value, Unit unit = Unit.Point)
         {
-            return element.Border(x => x.Left = value.ToPoints(unit));
+            value = value.ToPoints(unit);
+            return element.Border(left: value);
         }
         
         /// <summary>
@@ -63,7 +65,8 @@ namespace QuestPDF.Fluent
         /// </summary>
         public static IContainer BorderRight(this IContainer element, float value, Unit unit = Unit.Point)
         {
-            return element.Border(x => x.Right = value.ToPoints(unit));
+            value = value.ToPoints(unit);
+            return element.Border(right: value);
         }
         
         /// <summary>
@@ -72,7 +75,8 @@ namespace QuestPDF.Fluent
         /// </summary>
         public static IContainer BorderTop(this IContainer element, float value, Unit unit = Unit.Point)
         {
-            return element.Border(x => x.Top = value.ToPoints(unit));
+            value = value.ToPoints(unit);
+            return element.Border(top: value);
         }
         
         /// <summary>
@@ -81,7 +85,8 @@ namespace QuestPDF.Fluent
         /// </summary>        
         public static IContainer BorderBottom(this IContainer element, float value, Unit unit = Unit.Point)
         {
-            return element.Border(x => x.Bottom = value.ToPoints(unit));
+            value = value.ToPoints(unit);
+            return element.Border(bottom: value);
         }
         
         /// <summary>
@@ -92,7 +97,10 @@ namespace QuestPDF.Fluent
         public static IContainer BorderColor(this IContainer element, string color)
         {
             ColorValidator.Validate(color);
-            return element.Border(x => x.Color = color);
+            
+            var border = element as Border ?? new Border();
+            border.Color = color;
+            return element.Element(border);
         }
     }
 }

+ 40 - 12
Source/QuestPDF/Fluent/ConstrainedExtensions.cs

@@ -6,10 +6,17 @@ namespace QuestPDF.Fluent
 {
     public static class ConstrainedExtensions
     {
-        private static IContainer Constrained(this IContainer element, Action<Constrained> handler)
+        #region Width
+        
+        private static IContainer ConstrainedWidth(this IContainer element, float? min = null, float? max = null)
         {
             var constrained = element as Constrained ?? new Constrained();
-            handler(constrained);
+
+            if (min.HasValue)
+                constrained.MinWidth = min;
+            
+            if (max.HasValue)
+                constrained.MaxWidth = max;
             
             return element.Element(constrained);
         }
@@ -21,9 +28,8 @@ namespace QuestPDF.Fluent
         /// <returns>The container with the specified exact width.</returns>
         public static IContainer Width(this IContainer element, float value, Unit unit = Unit.Point)
         {
-            return element
-                .MinWidth(value, unit)
-                .MaxWidth(value, unit);
+            value = value.ToPoints(unit);
+            return element.ConstrainedWidth(min: value, max: value);
         }
         
         /// <summary>
@@ -33,7 +39,8 @@ namespace QuestPDF.Fluent
         /// <returns>The container with the specified minimum width.</returns>
         public static IContainer MinWidth(this IContainer element, float value, Unit unit = Unit.Point)
         {
-            return element.Constrained(x => x.MinWidth = value.ToPoints(unit));
+            value = value.ToPoints(unit);
+            return element.ConstrainedWidth(min: value);
         }
         
         /// <summary>
@@ -43,7 +50,25 @@ namespace QuestPDF.Fluent
         /// <returns>The container with the specified maximum width.</returns>
         public static IContainer MaxWidth(this IContainer element, float value, Unit unit = Unit.Point)
         {
-            return element.Constrained(x => x.MaxWidth = value.ToPoints(unit));
+            value = value.ToPoints(unit);
+            return element.ConstrainedWidth(max: value);
+        }
+        
+        #endregion
+        
+        #region Height
+        
+        private static IContainer ConstrainedHeight(this IContainer element, float? min = null, float? max = null)
+        {
+            var constrained = element as Constrained ?? new Constrained();
+
+            if (min.HasValue) 
+                constrained.MinHeight = min;
+            
+            if (max.HasValue)
+                constrained.MaxHeight = max;
+            
+            return element.Element(constrained);
         }
         
         /// <summary>
@@ -53,9 +78,8 @@ namespace QuestPDF.Fluent
         /// <returns>The container with the specified exact height.</returns>
         public static IContainer Height(this IContainer element, float value, Unit unit = Unit.Point)
         {
-            return element
-                .MinHeight(value, unit)
-                .MaxHeight(value, unit);
+            value = value.ToPoints(unit);
+            return element.ConstrainedHeight(min: value, max: value);
         }
         
         /// <summary>
@@ -65,7 +89,8 @@ namespace QuestPDF.Fluent
         /// <returns>The container with the specified minimum height.</returns>
         public static IContainer MinHeight(this IContainer element, float value, Unit unit = Unit.Point)
         {
-            return element.Constrained(x => x.MinHeight = value.ToPoints(unit));
+            value = value.ToPoints(unit);
+            return element.ConstrainedHeight(min: value);
         }
         
         /// <summary>
@@ -75,7 +100,10 @@ namespace QuestPDF.Fluent
         /// <returns>The container with the specified maximum height.</returns>
         public static IContainer MaxHeight(this IContainer element, float value, Unit unit = Unit.Point)
         {
-            return element.Constrained(x => x.MaxHeight = value.ToPoints(unit));
+            value = value.ToPoints(unit);
+            return element.ConstrainedHeight(max: value);
         }
+        
+        #endregion
     }
 }

+ 7 - 5
Source/QuestPDF/Fluent/ExtendExtensions.cs

@@ -6,10 +6,12 @@ namespace QuestPDF.Fluent
 {
     public static class ExtendExtensions
     {
-        private static IContainer Extend(this IContainer element, Action<Extend> handler)
+        private static IContainer Extend(this IContainer element, bool vertical = false, bool horizontal = false)
         {
             var extend = element as Extend ?? new Extend();
-            handler(extend);
+
+            extend.ExtendVertical |= vertical;
+            extend.ExtendHorizontal |= horizontal;
             
             return element.Element(extend);
         }
@@ -20,7 +22,7 @@ namespace QuestPDF.Fluent
         /// </summary>
         public static IContainer Extend(this IContainer element)
         {
-            return element.ExtendVertical().ExtendHorizontal();
+            return element.Extend(horizontal: true, vertical: true);
         }
         
         /// <summary>
@@ -29,7 +31,7 @@ namespace QuestPDF.Fluent
         /// </summary>
         public static IContainer ExtendVertical(this IContainer element)
         {
-            return element.Extend(x => x.ExtendVertical = true);
+            return element.Extend(vertical: true);
         }
         
         /// <summary>
@@ -38,7 +40,7 @@ namespace QuestPDF.Fluent
         /// </summary>
         public static IContainer ExtendHorizontal(this IContainer element)
         {
-            return element.Extend(x => x.ExtendHorizontal = true);
+            return element.Extend(horizontal: true);
         }
     }
 }

+ 20 - 15
Source/QuestPDF/Fluent/PaddingExtensions.cs

@@ -6,10 +6,14 @@ namespace QuestPDF.Fluent
 {
     public static class PaddingExtensions
     { 
-        private static IContainer Padding(this IContainer element, Action<Padding> handler)
+        private static IContainer Padding(this IContainer element, float top = 0, float bottom = 0, float left = 0, float right = 0)
         {
             var padding = element as Padding ?? new Padding();
-            handler(padding);
+
+            padding.Top += top;
+            padding.Bottom += bottom;
+            padding.Left += left;
+            padding.Right += right;
             
             return element.Element(padding);
         }
@@ -22,9 +26,8 @@ namespace QuestPDF.Fluent
         /// </summary>
         public static IContainer Padding(this IContainer element, float value, Unit unit = Unit.Point)
         {
-            return element
-                .PaddingVertical(value, unit)
-                .PaddingHorizontal(value, unit);
+            value = value.ToPoints(unit);
+            return element.Padding(top: value, bottom: value, left: value, right: value);
         }
         
         /// <summary>
@@ -35,9 +38,8 @@ namespace QuestPDF.Fluent
         /// </summary>
         public static IContainer PaddingHorizontal(this IContainer element, float value, Unit unit = Unit.Point)
         {
-            return element
-                .PaddingLeft(value, unit)
-                .PaddingRight(value, unit);
+            value = value.ToPoints(unit);
+            return element.Padding(left: value, right: value);
         }
 
         /// <summary>
@@ -48,9 +50,8 @@ namespace QuestPDF.Fluent
         /// </summary>
         public static IContainer PaddingVertical(this IContainer element, float value, Unit unit = Unit.Point)
         {
-            return element
-                .PaddingTop(value, unit)
-                .PaddingBottom(value, unit);
+            value = value.ToPoints(unit);
+            return element.Padding(top: value, bottom: value);
         }
         
         /// <summary>
@@ -61,7 +62,8 @@ namespace QuestPDF.Fluent
         /// </summary>
         public static IContainer PaddingTop(this IContainer element, float value, Unit unit = Unit.Point)
         {
-            return element.Padding(x => x.Top += value.ToPoints(unit));
+            value = value.ToPoints(unit);
+            return element.Padding(top: value);
         }
         
         /// <summary>
@@ -72,7 +74,8 @@ namespace QuestPDF.Fluent
         /// </summary>
         public static IContainer PaddingBottom(this IContainer element, float value, Unit unit = Unit.Point)
         {
-            return element.Padding(x => x.Bottom += value.ToPoints(unit));
+            value = value.ToPoints(unit);
+            return element.Padding(bottom: value);
         }
         
         /// <summary>
@@ -83,7 +86,8 @@ namespace QuestPDF.Fluent
         /// </summary>
         public static IContainer PaddingLeft(this IContainer element, float value, Unit unit = Unit.Point)
         {
-            return element.Padding(x => x.Left += value.ToPoints(unit));
+            value = value.ToPoints(unit);
+            return element.Padding(left: value);
         }
         
         /// <summary>
@@ -94,7 +98,8 @@ namespace QuestPDF.Fluent
         /// </summary>
         public static IContainer PaddingRight(this IContainer element, float value, Unit unit = Unit.Point)
         {
-            return element.Padding(x => x.Right += value.ToPoints(unit));
+            value = value.ToPoints(unit);
+            return element.Padding(right: value);
         }
     }
 }

+ 4 - 5
Source/QuestPDF/Fluent/RotateExtensions.cs

@@ -6,11 +6,10 @@ namespace QuestPDF.Fluent
 {
     public static class RotateExtensions
     {
-        private static IContainer SimpleRotate(this IContainer element, Action<SimpleRotate> handler)
+        private static IContainer SimpleRotate(this IContainer element, int turnDirection)
         {
             var scale = element as SimpleRotate ?? new SimpleRotate();
-            handler(scale);
-            
+            scale.TurnCount += turnDirection;
             return element.Element(scale);
         }
         
@@ -23,7 +22,7 @@ namespace QuestPDF.Fluent
         /// </remarks>
         public static IContainer RotateLeft(this IContainer element)
         {
-            return element.SimpleRotate(x => x.TurnCount--);
+            return element.SimpleRotate(-1);
         }
         
         /// <summary>
@@ -35,7 +34,7 @@ namespace QuestPDF.Fluent
         /// </remarks>
         public static IContainer RotateRight(this IContainer element)
         {
-            return element.SimpleRotate(x => x.TurnCount++);
+            return element.SimpleRotate(1);
         }
         
         /// <summary>

+ 3 - 3
Source/QuestPDF/Fluent/RowExtensions.cs

@@ -1,4 +1,4 @@
-using System;
+using System;
 using QuestPDF.Drawing.Exceptions;
 using QuestPDF.Elements;
 using QuestPDF.Infrastructure;
@@ -12,9 +12,9 @@ namespace QuestPDF.Fluent
         /// <summary>
         /// Adjusts horizontal spacing between items.
         /// </summary>
-        public void Spacing(float value)
+        public void Spacing(float value, Unit unit = Unit.Point)
         {
-            Row.Spacing = value;
+            Row.Spacing = value.ToPoints(unit);
         }
 
         private IContainer Item(RowItemType type, float size = 0)

+ 10 - 8
Source/QuestPDF/Fluent/ScaleExtensions.cs

@@ -6,10 +6,12 @@ namespace QuestPDF.Fluent
 {
     public static class ScaleExtensions
     {
-        private static IContainer Scale(this IContainer element, Action<Scale> handler)
+        private static IContainer ScaleValue(this IContainer element, float x = 1, float y = 1)
         {
             var scale = element as Scale ?? new Scale();
-            handler(scale);
+
+            scale.ScaleX *= x;
+            scale.ScaleY *= y;
             
             return element.Element(scale);
         }
@@ -22,7 +24,7 @@ namespace QuestPDF.Fluent
         /// <include file='../Resources/Documentation.xml' path='documentation/doc[@for="scale.factorParam"]/*' />
         public static IContainer Scale(this IContainer element, float factor)
         {
-            return element.ScaleHorizontal(factor).ScaleVertical(factor);
+            return element.ScaleValue(x: factor, y: factor);
         }
         
         /// <summary>
@@ -33,7 +35,7 @@ namespace QuestPDF.Fluent
         /// <include file='../Resources/Documentation.xml' path='documentation/doc[@for="scale.factorParam"]/*' />
         public static IContainer ScaleHorizontal(this IContainer element, float factor)
         {
-            return element.Scale(x => x.ScaleX *= factor);
+            return element.ScaleValue(x: factor);
         }
         
         /// <summary>
@@ -44,7 +46,7 @@ namespace QuestPDF.Fluent
         /// <include file='../Resources/Documentation.xml' path='documentation/doc[@for="scale.factorParam"]/*' />
         public static IContainer ScaleVertical(this IContainer element, float factor)
         {
-            return element.Scale(x => x.ScaleY *= factor);
+            return element.ScaleValue(y: factor);
         }
         
         /// <summary>
@@ -56,7 +58,7 @@ namespace QuestPDF.Fluent
         /// </example>
         public static IContainer FlipHorizontal(this IContainer element)
         {
-            return element.ScaleHorizontal(-1);
+            return element.ScaleValue(x: -1);
         }
         
         /// <summary>
@@ -68,7 +70,7 @@ namespace QuestPDF.Fluent
         /// </example>
         public static IContainer FlipVertical(this IContainer element)
         {
-            return element.ScaleVertical(-1);
+            return element.ScaleValue(y: -1);
         }
         
         /// <summary>
@@ -80,7 +82,7 @@ namespace QuestPDF.Fluent
         /// </example>
         public static IContainer FlipOver(this IContainer element)
         {
-            return element.FlipHorizontal().FlipVertical();
+            return element.ScaleValue(x: -1, y: -1);
         }
     }
 }

+ 16 - 14
Source/QuestPDF/Fluent/TableExtensions.cs

@@ -66,8 +66,6 @@ namespace QuestPDF.Fluent
     
     public class TableDescriptor
     {
-        internal List<TableColumnDefinition> Columns { get; private set; }
-
         private Table HeaderTable { get; } = new();
         private Table ContentTable { get; } = new();
         private Table FooterTable { get; } = new();
@@ -183,14 +181,6 @@ namespace QuestPDF.Fluent
 
     public static class TableCellExtensions
     {
-        private static ITableCellContainer TableCell(this ITableCellContainer element, Action<TableCell> handler)
-        {
-            if (element is TableCell tableCell)
-                handler(tableCell);
-            
-            return element;
-        }
-        
         /// <summary>
         /// Specifies the column position (horizontal axis) of the cell.
         /// <a href="https://www.questpdf.com/api-reference/table.html#basic-usage">Learn more</a>
@@ -198,7 +188,10 @@ namespace QuestPDF.Fluent
         /// <param name="value">Columns are numbered starting with 1.</param>
         public static ITableCellContainer Column(this ITableCellContainer tableCellContainer, uint value)
         {
-            return tableCellContainer.TableCell(x => x.Column = (int)value);
+            if (tableCellContainer is TableCell tableCell)
+                tableCell.Column = (int)value;
+
+            return tableCellContainer;
         }
         
         /// <summary>
@@ -210,7 +203,10 @@ namespace QuestPDF.Fluent
         /// </remarks>
         public static ITableCellContainer ColumnSpan(this ITableCellContainer tableCellContainer, uint value)
         {
-            return tableCellContainer.TableCell(x => x.ColumnSpan = (int)value);
+            if (tableCellContainer is TableCell tableCell)
+                tableCell.ColumnSpan = (int)value;
+
+            return tableCellContainer;
         }
         
         /// <summary>
@@ -220,7 +216,10 @@ namespace QuestPDF.Fluent
         /// <param name="value">Rows are numbered starting with 1.</param>
         public static ITableCellContainer Row(this ITableCellContainer tableCellContainer, uint value)
         {
-            return tableCellContainer.TableCell(x => x.Row = (int)value);
+            if (tableCellContainer is TableCell tableCell)
+                tableCell.Row = (int)value;
+
+            return tableCellContainer;
         }
         
         /// <summary>
@@ -232,7 +231,10 @@ namespace QuestPDF.Fluent
         /// </remarks>
         public static ITableCellContainer RowSpan(this ITableCellContainer tableCellContainer, uint value)
         {
-            return tableCellContainer.TableCell(x => x.RowSpan = (int)value);
+            if (tableCellContainer is TableCell tableCell)
+                tableCell.RowSpan = (int)value;
+
+            return tableCellContainer;
         }
     }
 }

+ 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;
         }
     }
 }

+ 28 - 28
Source/QuestPDF/Fluent/TextSpanDescriptorExtensions.cs

@@ -13,14 +13,14 @@ namespace QuestPDF.Fluent
             if (style == null)
                 return descriptor;
             
-            descriptor.MutateTextStyle(x => x.OverrideStyle(style));
+            descriptor.MutateTextStyle(TextStyleManager.OverrideStyle, style);
             return descriptor;
         }
         
         /// <include file='../Resources/Documentation.xml' path='documentation/doc[@for="text.fontFallback"]/*' />
         public static T Fallback<T>(this T descriptor, TextStyle? value = null) where T : TextSpanDescriptor
         {
-            descriptor.TextStyle.Fallback = value;
+            descriptor.MutateTextStyle(TextStyleExtensions.Fallback, value);
             return descriptor;
         }
         
@@ -35,7 +35,7 @@ namespace QuestPDF.Fluent
         public static T FontColor<T>(this T descriptor, string color) where T : TextSpanDescriptor
         {
             ColorValidator.Validate(color);
-            descriptor.MutateTextStyle(x => x.FontColor(color));
+            descriptor.MutateTextStyle(TextStyleExtensions.FontColor, color);
             return descriptor;
         }
         
@@ -44,14 +44,14 @@ namespace QuestPDF.Fluent
         public static T BackgroundColor<T>(this T descriptor, string color) where T : TextSpanDescriptor
         {
             ColorValidator.Validate(color);
-            descriptor.MutateTextStyle(x => x.BackgroundColor(color));
+            descriptor.MutateTextStyle(TextStyleExtensions.BackgroundColor, color);
             return descriptor;
         }
         
         /// <include file='../Resources/Documentation.xml' path='documentation/doc[@for="text.fontFamily"]/*' />
         public static T FontFamily<T>(this T descriptor, string value) where T : TextSpanDescriptor
         {
-            descriptor.MutateTextStyle(x => x.FontFamily(value));
+            descriptor.MutateTextStyle(TextStyleExtensions.FontFamily, value);
             return descriptor;
         }
         
@@ -61,7 +61,7 @@ namespace QuestPDF.Fluent
             if (value <= 0)
                 throw new ArgumentException("Font size must be greater than 0.");
             
-            descriptor.MutateTextStyle(x => x.FontSize(value));
+            descriptor.MutateTextStyle(TextStyleExtensions.FontSize, value);
             return descriptor;
         }
         
@@ -71,7 +71,7 @@ namespace QuestPDF.Fluent
             if (factor <= 0)
                 throw new ArgumentException("Line height must be greater than 0.");
             
-            descriptor.MutateTextStyle(x => x.LineHeight(factor));
+            descriptor.MutateTextStyle(TextStyleExtensions.LineHeight, factor);
             return descriptor;
         }
 
@@ -81,35 +81,35 @@ namespace QuestPDF.Fluent
             if (factor <= 0)
                 throw new ArgumentException("Letter spacing must be greater than 0.");
             
-            descriptor.MutateTextStyle(x => x.LetterSpacing(factor));
+            descriptor.MutateTextStyle(TextStyleExtensions.LetterSpacing, factor);
             return descriptor;
         }
         
         /// <include file='../Resources/Documentation.xml' path='documentation/doc[@for="text.italic"]/*' />
         public static T Italic<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor
         {
-            descriptor.MutateTextStyle(x => x.Italic(value));
+            descriptor.MutateTextStyle(TextStyleExtensions.Italic, value);
             return descriptor;
         }
         
         /// <include file='../Resources/Documentation.xml' path='documentation/doc[@for="text.strikethrough"]/*' />
         public static T Strikethrough<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor
         {
-            descriptor.MutateTextStyle(x => x.Strikethrough(value));
+            descriptor.MutateTextStyle(TextStyleExtensions.Strikethrough, value);
             return descriptor;
         }
         
         /// <include file='../Resources/Documentation.xml' path='documentation/doc[@for="text.underline"]/*' />
         public static T Underline<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor
         {
-            descriptor.MutateTextStyle(x => x.Underline(value));
+            descriptor.MutateTextStyle(TextStyleExtensions.Underline, value);
             return descriptor;
         }
 
         /// <include file='../Resources/Documentation.xml' path='documentation/doc[@for="text.wrapAnywhere"]/*' />
         public static T WrapAnywhere<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor
         {
-            descriptor.MutateTextStyle(x => x.WrapAnywhere(value));
+            descriptor.MutateTextStyle(TextStyleExtensions.WrapAnywhere, value);
             return descriptor;
         }
 
@@ -119,7 +119,7 @@ namespace QuestPDF.Fluent
         /// <include file='../Resources/Documentation.xml' path='documentation/doc[@for="text.weight.remarks"]/*' />
         public static T Thin<T>(this T descriptor) where T : TextSpanDescriptor
         {
-            descriptor.MutateTextStyle(x => x.Thin());
+            descriptor.MutateTextStyle(TextStyleExtensions.Thin);
             return descriptor;
         }
         
@@ -127,7 +127,7 @@ namespace QuestPDF.Fluent
         /// <include file='../Resources/Documentation.xml' path='documentation/doc[@for="text.weight.remarks"]/*' />
         public static T ExtraLight<T>(this T descriptor) where T : TextSpanDescriptor
         {
-            descriptor.MutateTextStyle(x => x.ExtraLight());
+            descriptor.MutateTextStyle(TextStyleExtensions.ExtraLight);
             return descriptor;
         }
         
@@ -135,7 +135,7 @@ namespace QuestPDF.Fluent
         /// <include file='../Resources/Documentation.xml' path='documentation/doc[@for="text.weight.remarks"]/*' />
         public static T Light<T>(this T descriptor) where T : TextSpanDescriptor
         {
-            descriptor.MutateTextStyle(x => x.Light());
+            descriptor.MutateTextStyle(TextStyleExtensions.Light);
             return descriptor;
         }
         
@@ -143,7 +143,7 @@ namespace QuestPDF.Fluent
         /// <include file='../Resources/Documentation.xml' path='documentation/doc[@for="text.weight.remarks"]/*' />
         public static T NormalWeight<T>(this T descriptor) where T : TextSpanDescriptor
         {
-            descriptor.MutateTextStyle(x => x.NormalWeight());
+            descriptor.MutateTextStyle(TextStyleExtensions.NormalWeight);
             return descriptor;
         }
         
@@ -151,7 +151,7 @@ namespace QuestPDF.Fluent
         /// <include file='../Resources/Documentation.xml' path='documentation/doc[@for="text.weight.remarks"]/*' />
         public static T Medium<T>(this T descriptor) where T : TextSpanDescriptor
         {
-            descriptor.MutateTextStyle(x => x.Medium());
+            descriptor.MutateTextStyle(TextStyleExtensions.Medium);
             return descriptor;
         }
         
@@ -159,7 +159,7 @@ namespace QuestPDF.Fluent
         /// <include file='../Resources/Documentation.xml' path='documentation/doc[@for="text.weight.remarks"]/*' />
         public static T SemiBold<T>(this T descriptor) where T : TextSpanDescriptor
         {
-            descriptor.MutateTextStyle(x => x.SemiBold());
+            descriptor.MutateTextStyle(TextStyleExtensions.SemiBold);
             return descriptor;
         }
         
@@ -167,7 +167,7 @@ namespace QuestPDF.Fluent
         /// <include file='../Resources/Documentation.xml' path='documentation/doc[@for="text.weight.remarks"]/*' />
         public static T Bold<T>(this T descriptor) where T : TextSpanDescriptor
         {
-            descriptor.MutateTextStyle(x => x.Bold());
+            descriptor.MutateTextStyle(TextStyleExtensions.Bold);
             return descriptor;
         }
         
@@ -175,7 +175,7 @@ namespace QuestPDF.Fluent
         /// <include file='../Resources/Documentation.xml' path='documentation/doc[@for="text.weight.remarks"]/*' />
         public static T ExtraBold<T>(this T descriptor) where T : TextSpanDescriptor
         {
-            descriptor.MutateTextStyle(x => x.ExtraBold());
+            descriptor.MutateTextStyle(TextStyleExtensions.ExtraBold);
             return descriptor;
         }
         
@@ -183,7 +183,7 @@ namespace QuestPDF.Fluent
         /// <include file='../Resources/Documentation.xml' path='documentation/doc[@for="text.weight.remarks"]/*' />
         public static T Black<T>(this T descriptor) where T : TextSpanDescriptor
         {
-            descriptor.MutateTextStyle(x => x.Black());
+            descriptor.MutateTextStyle(TextStyleExtensions.Black);
             return descriptor;
         }
         
@@ -191,7 +191,7 @@ namespace QuestPDF.Fluent
         /// <include file='../Resources/Documentation.xml' path='documentation/doc[@for="text.weight.remarks"]/*' />
         public static T ExtraBlack<T>(this T descriptor) where T : TextSpanDescriptor
         {
-            descriptor.MutateTextStyle(x => x.ExtraBlack());
+            descriptor.MutateTextStyle(TextStyleExtensions.ExtraBlack);
             return descriptor;
         }
 
@@ -202,21 +202,21 @@ namespace QuestPDF.Fluent
         /// <include file='../Resources/Documentation.xml' path='documentation/doc[@for="text.position.normal"]/*' />
         public static T NormalPosition<T>(this T descriptor) where T : TextSpanDescriptor
         {
-            descriptor.MutateTextStyle(x => x.NormalPosition());
+            descriptor.MutateTextStyle(TextStyleExtensions.NormalPosition);
             return descriptor;
         }
 
         /// <include file='../Resources/Documentation.xml' path='documentation/doc[@for="text.position.subscript"]/*' />
         public static T Subscript<T>(this T descriptor) where T : TextSpanDescriptor
         {
-            descriptor.MutateTextStyle(x => x.Subscript());
+            descriptor.MutateTextStyle(TextStyleExtensions.Subscript);
             return descriptor;
         }
 
         /// <include file='../Resources/Documentation.xml' path='documentation/doc[@for="text.position.superscript"]/*' />
         public static T Superscript<T>(this T descriptor) where T : TextSpanDescriptor
         {
-            descriptor.MutateTextStyle(x => x.Superscript());
+            descriptor.MutateTextStyle(TextStyleExtensions.Superscript);
             return descriptor;
         }
         
@@ -227,21 +227,21 @@ namespace QuestPDF.Fluent
         /// <include file='../Resources/Documentation.xml' path='documentation/doc[@for="text.direction.auto"]/*' />
         public static T DirectionAuto<T>(this T descriptor) where T : TextSpanDescriptor
         {
-            descriptor.MutateTextStyle(x => x.DirectionAuto());
+            descriptor.MutateTextStyle(TextStyleExtensions.DirectionAuto);
             return descriptor;
         }
         
         /// <include file='../Resources/Documentation.xml' path='documentation/doc[@for="text.direction.ltr"]/*' />
         public static T DirectionFromLeftToRight<T>(this T descriptor) where T : TextSpanDescriptor
         {
-            descriptor.MutateTextStyle(x => x.DirectionFromLeftToRight());
+            descriptor.MutateTextStyle(TextStyleExtensions.DirectionFromLeftToRight);
             return descriptor;
         }
         
         /// <include file='../Resources/Documentation.xml' path='documentation/doc[@for="text.direction.rtl"]/*' />
         public static T DirectionFromRightToLeft<T>(this T descriptor) where T : TextSpanDescriptor
         {
-            descriptor.MutateTextStyle(x => x.DirectionFromRightToLeft());
+            descriptor.MutateTextStyle(TextStyleExtensions.DirectionFromRightToLeft);
             return descriptor;
         }
 

+ 8 - 4
Source/QuestPDF/Fluent/TranslateExtensions.cs

@@ -6,10 +6,12 @@ namespace QuestPDF.Fluent
 {
     public static class TranslateExtensions
     {
-        private static IContainer Translate(this IContainer element, Action<Translate> handler)
+        private static IContainer Translate(this IContainer element, float x = 0, float y = 0)
         {
             var translate = element as Translate ?? new Translate();
-            handler(translate);
+
+            translate.TranslateX += x;
+            translate.TranslateY += y;
             
             return element.Element(translate);
         }
@@ -23,7 +25,8 @@ namespace QuestPDF.Fluent
         /// </summary>
         public static IContainer TranslateX(this IContainer element, float value, Unit unit = Unit.Point)
         {
-            return element.Translate(x => x.TranslateX += value.ToPoints(unit));
+            value = value.ToPoints(unit);
+            return element.Translate(x: value);
         }
    
         /// <summary>
@@ -35,7 +38,8 @@ namespace QuestPDF.Fluent
         /// </summary>
         public static IContainer TranslateY(this IContainer element, float value, Unit unit = Unit.Point)
         {
-            return element.Translate(x => x.TranslateY += value.ToPoints(unit));
+            value = value.ToPoints(unit);
+            return element.Translate(y: value);
         }
     }
 }

+ 3 - 3
Source/QuestPDF/Helpers/NativeDependencyCompatibilityChecker.cs

@@ -43,10 +43,10 @@ namespace QuestPDF.Helpers
             try
             {
                 // accessing any SkiaSharp object triggers loading of SkiaSharp-related DLL dependency
-                using var typeface = new SkiaSharp.SKPaint();
+                using var paint = new SkiaSharp.SKPaint();
 
                 // accessing any HarfBuzzSharp object triggers loading of HarfBuzz-related DLL dependency
-                using var shaper = new HarfBuzzSharp.Buffer();
+                using var buffer = new HarfBuzzSharp.Buffer();
 
                 // everything loads and works correctly
                 return null;
@@ -131,4 +131,4 @@ namespace QuestPDF.Helpers
             #endif
         }
     }
-}
+}