Browse Source

Merge pull request #32 from QuestPDF/global-text-style

Global text style
Marcin Ziąbek 4 years ago
parent
commit
1371fe1df9

+ 2 - 1
QuestPDF.ReportSample/Layouts/ImageTemplate.cs

@@ -1,5 +1,6 @@
 using System;
 using QuestPDF.Fluent;
+using QuestPDF.Helpers;
 using QuestPDF.Infrastructure;
 
 namespace QuestPDF.ReportSample.Layouts
@@ -24,7 +25,7 @@ namespace QuestPDF.ReportSample.Layouts
         {
             container
                 .AspectRatio(AspectRatio)
-                .Background("#EEEEEE")
+                .Background(Colors.Grey.Lighten3)
                 .Image(Source(Size.Zero));
         }
     }

+ 6 - 6
QuestPDF.ReportSample/Layouts/PhotoTemplate.cs

@@ -48,13 +48,13 @@ namespace QuestPDF.ReportSample.Layouts
             {
                 grid.Columns(6);
                 
-                grid.Item().LabelCell().Text("Date", Typography.Normal);
-                grid.Item(2).ValueCell().Text(Model.Date?.ToString("g") ?? string.Empty, Typography.Normal);
-                grid.Item().LabelCell().Text("Location", Typography.Normal);
-                grid.Item(2).ValueCell().Text(Model.Location.Format(), Typography.Normal);
+                grid.Item().LabelCell().Text("Date");
+                grid.Item(2).ValueCell().Text(Model.Date?.ToString("g") ?? string.Empty);
+                grid.Item().LabelCell().Text("Location");
+                grid.Item(2).ValueCell().Text(Model.Location.Format());
                 
-                grid.Item().LabelCell().Text("Comments", Typography.Normal);
-                grid.Item(5).ValueCell().Text(Model.Comments, Typography.Normal);
+                grid.Item().LabelCell().Text("Comments");
+                grid.Item(5).ValueCell().Text(Model.Comments);
             });
         }
     }

+ 5 - 5
QuestPDF.ReportSample/Layouts/SectionTemplate.cs

@@ -30,11 +30,11 @@ namespace QuestPDF.ReportSample.Layouts
                         {
                             stack.Item().EnsureSpace(25).Row(row =>
                             {
-                                row.ConstantColumn(150).LabelCell().Text(part.Label, Typography.Normal);
+                                row.ConstantColumn(150).LabelCell().Text(part.Label);
                                 var frame = row.RelativeColumn().ValueCell();
                             
                                 if (part is ReportSectionText text)
-                                    frame.ShowEntire().Text(text.Text, Typography.Normal);
+                                    frame.ShowEntire().Text(text.Text);
                         
                                 if (part is ReportSectionMap map)
                                     frame.Element(x => MapElement(x, map));
@@ -51,7 +51,7 @@ namespace QuestPDF.ReportSample.Layouts
         {
             if (model.ImageSource == null || model.Location == null)
             {
-                container.Text("No location provided", Typography.Normal);
+                container.Text("No location provided");
                 return;
             }
 
@@ -60,7 +60,7 @@ namespace QuestPDF.ReportSample.Layouts
                 stack.Spacing(5);
                 
                 stack.Item().MaxWidth(250).AspectRatio(4 / 3f).Image(Placeholders.Image);
-                stack.Item().Text(model.Location.Format(), Typography.Normal);
+                stack.Item().Text(model.Location.Format());
             });
         }
         
@@ -68,7 +68,7 @@ namespace QuestPDF.ReportSample.Layouts
         {
             if (model.Photos.Count == 0)
             {
-                container.Text("No photos", Typography.Normal);
+                container.Text("No photos");
                 return;
             }
 

+ 3 - 4
QuestPDF.ReportSample/Layouts/StandardReport.cs

@@ -25,6 +25,7 @@ namespace QuestPDF.ReportSample.Layouts
         public void Compose(IDocumentContainer container)
         {
             container
+                .DefaultTextStyle(Typography.Normal)
                 .Page(page =>
                 {
                     page.MarginVertical(40);
@@ -37,8 +38,6 @@ namespace QuestPDF.ReportSample.Layouts
                     
                     page.Footer().AlignCenter().Text(text =>
                     {
-                        text.DefaultTextStyle(Typography.Normal);
-                        
                         text.CurrentPageNumber();
                         text.Span(" / ");
                         text.TotalPages();
@@ -69,8 +68,8 @@ namespace QuestPDF.ReportSample.Layouts
                     {
                         grid.Item().Text(text =>
                         {
-                            text.Span($"{field.Label}: ", Typography.Normal.SemiBold());
-                            text.Span(field.Value, Typography.Normal);
+                            text.Span($"{field.Label}: ", TextStyle.Default.SemiBold());
+                            text.Span(field.Value);
                         });
                     }
                 });

+ 3 - 3
QuestPDF.ReportSample/Layouts/TableOfContentsTemplate.cs

@@ -41,9 +41,9 @@ namespace QuestPDF.ReportSample.Layouts
                 .InternalLink(locationName)
                 .Row(row =>
                 {
-                    row.ConstantColumn(25).Text($"{number}.", Typography.Normal);
-                    row.RelativeColumn().Text(locationName, Typography.Normal);
-                    row.ConstantColumn(150).AlignRight().Text(text => text.PageNumberOfLocation(locationName, Typography.Normal));
+                    row.ConstantColumn(25).Text($"{number}.");
+                    row.RelativeColumn().Text(locationName);
+                    row.ConstantColumn(150).AlignRight().Text(text => text.PageNumberOfLocation(locationName));
                 });
         }
     }

+ 1 - 1
QuestPDF.ReportSample/Typography.cs

@@ -8,6 +8,6 @@ namespace QuestPDF.ReportSample
     {
         public static TextStyle Title => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Blue.Darken3).Size(26).Black();
         public static TextStyle Headline => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Blue.Medium).Size(16).SemiBold();
-        public static TextStyle Normal => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Black).Size(11).LineHeight(1.1f);
+        public static TextStyle Normal => TextStyle.Default.FontType(Fonts.Verdana).Color(Colors.Black).Size(10).LineHeight(1.2f);
     }
 }

+ 1 - 0
QuestPDF/Drawing/DocumentContainer.cs

@@ -9,6 +9,7 @@ namespace QuestPDF.Drawing
 {
     internal class DocumentContainer : IDocumentContainer
     {
+        internal TextStyle DefaultTextStyle { get; set; } = TextStyle.Default;
         internal List<IComponent> Pages { get; set; } = new List<IComponent>();
         
         internal Container Compose()

+ 26 - 0
QuestPDF/Drawing/DocumentGenerator.cs

@@ -1,9 +1,12 @@
 using System;
 using System.Collections.Generic;
 using System.IO;
+using System.Linq;
 using QuestPDF.Drawing.Exceptions;
 using QuestPDF.Drawing.SpacePlan;
 using QuestPDF.Elements;
+using QuestPDF.Elements.Text;
+using QuestPDF.Elements.Text.Items;
 using QuestPDF.Infrastructure;
 
 namespace QuestPDF.Drawing
@@ -36,6 +39,7 @@ namespace QuestPDF.Drawing
             var metadata = document.GetMetadata();
             var pageContext = new PageContext();
 
+            ApplyDefaultTextStyle(content, container.DefaultTextStyle);
             RenderPass(pageContext, new FreeCanvas(), content, metadata);
             RenderPass(pageContext, canvas, content, metadata);
         }
@@ -93,5 +97,27 @@ namespace QuestPDF.Drawing
                 throw new DocumentLayoutException("Composed layout generates infinite document.");
             }
         }
+
+        private static void ApplyDefaultTextStyle(Element content, TextStyle documentDefaultTextStyle)
+        {
+            documentDefaultTextStyle.ApplyGlobalStyle(TextStyle.LibraryDefault);
+            
+            content.HandleVisitor(element =>
+            {
+                var text = element as TextBlock;
+                
+                if (text == null)
+                    return;
+
+                foreach (var child in text.Children)
+                {
+                    if (child is TextBlockSpan textSpan)
+                        textSpan.Style.ApplyGlobalStyle(documentDefaultTextStyle);
+
+                    if (child is TextBlockElement textElement)
+                        ApplyDefaultTextStyle(textElement.Element, documentDefaultTextStyle);
+                }
+            });
+        }
     }
 }

+ 5 - 5
QuestPDF/Drawing/FontManager.cs

@@ -34,7 +34,7 @@ namespace QuestPDF.Drawing
         
         internal static SKPaint ToPaint(this TextStyle style)
         {
-            return Paints.GetOrAdd(style.ToString(), key => Convert(style));
+            return Paints.GetOrAdd(style.Key, key => Convert(style));
             
             static SKPaint Convert(TextStyle style)
             {
@@ -42,7 +42,7 @@ namespace QuestPDF.Drawing
                 {
                     Color = SKColor.Parse(style.Color),
                     Typeface = GetTypeface(style),
-                    TextSize = style.Size,
+                    TextSize = (style.Size ?? 12),
                     TextEncoding = SKTextEncoding.Utf32
                 };
             }
@@ -52,16 +52,16 @@ namespace QuestPDF.Drawing
                 if (Typefaces.TryGetValue(style.FontType, out var result))
                     return result;
                 
-                var slant = style.IsItalic ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright;
+                var slant = (style.IsItalic ?? false) ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright;
                 
-                return SKTypeface.FromFamilyName(style.FontType, (int)style.FontWeight, (int)SKFontStyleWidth.Normal, slant) 
+                return SKTypeface.FromFamilyName(style.FontType, (int)(style.FontWeight ?? FontWeight.Normal), (int)SKFontStyleWidth.Normal, slant) 
                        ?? throw new ArgumentException($"The typeface {style.FontType} could not be found.");
             }
         }
 
         internal static SKFontMetrics ToFontMetrics(this TextStyle style)
         {
-            return FontMetrics.GetOrAdd(style.ToString(), key => style.ToPaint().FontMetrics);
+            return FontMetrics.GetOrAdd(style.Key, key => style.ToPaint().FontMetrics);
         }
     }
 }

+ 1 - 0
QuestPDF/Elements/Text/Items/ITextBlockItem.cs

@@ -1,4 +1,5 @@
 using QuestPDF.Elements.Text.Calculation;
+using QuestPDF.Infrastructure;
 
 namespace QuestPDF.Elements.Text.Items
 {

+ 5 - 16
QuestPDF/Elements/Text/Items/TextBlockExternalLink.cs

@@ -3,33 +3,22 @@ using QuestPDF.Infrastructure;
 
 namespace QuestPDF.Elements.Text.Items
 {
-    internal class TextBlockExternalLink : ITextBlockItem
+    internal class TextBlockExternalLink : TextBlockSpan
     {
-        public TextStyle Style { get; set; } = new TextStyle();
-        public string Text { get; set; }
         public string Url { get; set; }
         
-        public TextMeasurementResult? Measure(TextMeasurementRequest request)
+        public override TextMeasurementResult? Measure(TextMeasurementRequest request)
         {
-            return GetItem().MeasureWithoutCache(request);
+            return MeasureWithoutCache(request);
         }
 
-        public void Draw(TextDrawingRequest request)
+        public override void Draw(TextDrawingRequest request)
         {
             request.Canvas.Translate(new Position(0, request.TotalAscent));
             request.Canvas.DrawExternalLink(Url, new Size(request.TextSize.Width, request.TextSize.Height));
             request.Canvas.Translate(new Position(0, -request.TotalAscent));
             
-            GetItem().Draw(request);
-        }
-
-        private TextBlockSpan GetItem()
-        {
-            return new TextBlockSpan
-            {
-                Style = Style,
-                Text = Text
-            };
+            base.Draw(request);
         }
     }
 }

+ 5 - 16
QuestPDF/Elements/Text/Items/TextBlockInternalLink.cs

@@ -3,33 +3,22 @@ using QuestPDF.Infrastructure;
 
 namespace QuestPDF.Elements.Text.Items
 {
-    internal class TextBlockInternalLink : ITextBlockItem
+    internal class TextBlockInternalLink : TextBlockSpan
     {
-        public TextStyle Style { get; set; } = new TextStyle();
-        public string Text { get; set; }
         public string LocationName { get; set; }
         
-        public TextMeasurementResult? Measure(TextMeasurementRequest request)
+        public override TextMeasurementResult? Measure(TextMeasurementRequest request)
         {
-            return GetItem().MeasureWithoutCache(request);
+            return MeasureWithoutCache(request);
         }
 
-        public void Draw(TextDrawingRequest request)
+        public override void Draw(TextDrawingRequest request)
         {
             request.Canvas.Translate(new Position(0, request.TotalAscent));
             request.Canvas.DrawLocationLink(LocationName, new Size(request.TextSize.Width, request.TextSize.Height));
             request.Canvas.Translate(new Position(0, -request.TotalAscent));
             
-            GetItem().Draw(request);
-        }
-
-        private TextBlockSpan GetItem()
-        {
-            return new TextBlockSpan
-            {
-                Style = Style,
-                Text = Text
-            };
+            base.Draw(request);
         }
     }
 }

+ 10 - 13
QuestPDF/Elements/Text/Items/TextBlockPageNumber.cs

@@ -3,34 +3,31 @@ using QuestPDF.Infrastructure;
 
 namespace QuestPDF.Elements.Text.Items
 {
-    internal class TextBlockPageNumber : ITextBlockItem
+    internal class TextBlockPageNumber : TextBlockSpan
     {
-        public TextStyle Style { get; set; } = new TextStyle();
         public string SlotName { get; set; }
         
-        public TextMeasurementResult? Measure(TextMeasurementRequest request)
+        public override TextMeasurementResult? Measure(TextMeasurementRequest request)
         {
-            return GetItem(request.PageContext).MeasureWithoutCache(request);
+            SetPageNumber(request.PageContext);
+            return MeasureWithoutCache(request);
         }
 
-        public void Draw(TextDrawingRequest request)
+        public override void Draw(TextDrawingRequest request)
         {
-            GetItem(request.PageContext).Draw(request);
+            SetPageNumber(request.PageContext);
+            base.Draw(request);
         }
 
-        private TextBlockSpan GetItem(IPageContext context)
+        private void SetPageNumber(IPageContext context)
         {
             var pageNumberPlaceholder = 123;
             
             var pageNumber = context.GetRegisteredLocations().Contains(SlotName)
                 ? context.GetLocationPage(SlotName)
                 : pageNumberPlaceholder;
-            
-            return new TextBlockSpan
-            {
-                Style = Style,
-                Text = pageNumber.ToString()
-            };
+
+            Text = pageNumber.ToString();
         }
     }
 }

+ 8 - 8
QuestPDF/Elements/Text/Items/TextBlockSpan.cs

@@ -14,7 +14,7 @@ namespace QuestPDF.Elements.Text.Items
         private Dictionary<(int startIndex, float availableWidth), TextMeasurementResult?> MeasureCache =
             new Dictionary<(int startIndex, float availableWidth), TextMeasurementResult?>();
 
-        public TextMeasurementResult? Measure(TextMeasurementRequest request)
+        public virtual TextMeasurementResult? Measure(TextMeasurementRequest request)
         {
             var cacheKey = (request.StartIndex, request.AvailableWidth);
             
@@ -45,7 +45,7 @@ namespace QuestPDF.Elements.Text.Items
                 {
                     Width = 0,
                     
-                    LineHeight = Style.LineHeight,
+                    LineHeight = Style.LineHeight ?? 1,
                     Ascent = fontMetrics.Ascent,
                     Descent = fontMetrics.Descent
                 };
@@ -96,7 +96,7 @@ namespace QuestPDF.Elements.Text.Items
                 Ascent = fontMetrics.Ascent,
                 Descent = fontMetrics.Descent,
      
-                LineHeight = Style.LineHeight,
+                LineHeight = Style.LineHeight ?? 1,
                 
                 StartIndex = startIndex,
                 EndIndex = endIndex,
@@ -105,7 +105,7 @@ namespace QuestPDF.Elements.Text.Items
             };
         }
         
-        public void Draw(TextDrawingRequest request)
+        public virtual void Draw(TextDrawingRequest request)
         {
             var fontMetrics = Style.ToFontMetrics();
 
@@ -115,12 +115,12 @@ namespace QuestPDF.Elements.Text.Items
             request.Canvas.DrawText(text, Position.Zero, Style);
 
             // draw underline
-            if (Style.HasUnderline && fontMetrics.UnderlinePosition.HasValue)
-                DrawLine(fontMetrics.UnderlinePosition.Value, fontMetrics.UnderlineThickness.Value);
+            if ((Style.HasUnderline ?? false) && fontMetrics.UnderlinePosition.HasValue)
+                DrawLine(fontMetrics.UnderlinePosition.Value, fontMetrics.UnderlineThickness ?? 1);
             
             // draw stroke
-            if (Style.HasStrikethrough && fontMetrics.StrikeoutPosition.HasValue)
-                DrawLine(fontMetrics.StrikeoutPosition.Value, fontMetrics.StrikeoutThickness.Value);
+            if ((Style.HasStrikethrough ?? false) && fontMetrics.StrikeoutPosition.HasValue)
+                DrawLine(fontMetrics.StrikeoutPosition.Value, fontMetrics.StrikeoutThickness ?? 1);
 
             void DrawLine(float offset, float thickness)
             {

+ 6 - 0
QuestPDF/Fluent/PageExtensions.cs

@@ -94,6 +94,12 @@ namespace QuestPDF.Fluent
     
     public static class PageExtensions
     {
+        public static IDocumentContainer DefaultTextStyle(this IDocumentContainer document, TextStyle textStyle)
+        {
+            (document as DocumentContainer).DefaultTextStyle = textStyle;
+            return document;
+        }
+        
         public static IDocumentContainer Page(this IDocumentContainer document, Action<PageDescriptor> handler)
         {
             var descriptor = new PageDescriptor();

+ 12 - 9
QuestPDF/Fluent/TextExtensions.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using QuestPDF.Drawing;
 using QuestPDF.Elements;
 using QuestPDF.Elements.Text;
 using QuestPDF.Elements.Text.Items;
@@ -13,7 +14,7 @@ namespace QuestPDF.Fluent
     {
         private ICollection<TextBlock> TextBlocks { get; } = new List<TextBlock>();
         private TextStyle DefaultStyle { get; set; } = TextStyle.Default;
-        private HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
+        internal HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
         private float Spacing { get; set; } = 0f;
 
         public void DefaultTextStyle(TextStyle style)
@@ -51,7 +52,7 @@ namespace QuestPDF.Fluent
         
         public void Span(string text, TextStyle? style = null)
         {
-            style ??= DefaultStyle;
+            style ??= TextStyle.Default;
  
             var items = text
                 .Replace("\r", string.Empty)
@@ -92,7 +93,7 @@ namespace QuestPDF.Fluent
 
         private void PageNumber(string slotName, TextStyle? style = null)
         {
-            style ??= DefaultStyle;
+            style ??= TextStyle.Default;
             
             AddItemToLastTextBlock(new TextBlockPageNumber()
             {
@@ -118,7 +119,7 @@ namespace QuestPDF.Fluent
         
         public void InternalLocation(string text, string locationName, TextStyle? style = null)
         {
-            style ??= DefaultStyle;
+            style ??= TextStyle.Default;
             
             AddItemToLastTextBlock(new TextBlockInternalLink
             {
@@ -130,7 +131,7 @@ namespace QuestPDF.Fluent
         
         public void ExternalLocation(string text, string url, TextStyle? style = null)
         {
-            style ??= DefaultStyle;
+            style ??= TextStyle.Default;
             
             AddItemToLastTextBlock(new TextBlockExternalLink
             {
@@ -156,6 +157,9 @@ namespace QuestPDF.Fluent
         {
             TextBlocks.ToList().ForEach(x => x.Alignment = Alignment);
             
+            foreach (var textBlockSpan in TextBlocks.SelectMany(x => x.Children).Where(x => x is TextBlockSpan).Cast<TextBlockSpan>())
+                textBlockSpan.Style.ApplyParentStyle(DefaultStyle);
+
             container.Stack(stack =>
             {
                 stack.Spacing(Spacing);
@@ -170,12 +174,11 @@ namespace QuestPDF.Fluent
     {
         public static void Text(this IContainer element, Action<TextDescriptor> content)
         {
-            var textBlock = new TextBlock();
-
+            var descriptor = new TextDescriptor();
+            
             if (element is Alignment alignment)
-                textBlock.Alignment = alignment.Horizontal;
+                descriptor.Alignment = alignment.Horizontal;
             
-            var descriptor = new TextDescriptor();
             content?.Invoke(descriptor);
             descriptor.Compose(element);
         }

+ 57 - 16
QuestPDF/Infrastructure/TextStyle.cs

@@ -1,29 +1,70 @@
-using QuestPDF.Helpers;
+using System;
+using QuestPDF.Helpers;
 
 namespace QuestPDF.Infrastructure
 {
     public class TextStyle
     {
-        internal string Color { get; set; } = Colors.Black;
-        internal string BackgroundColor { get; set; } = Colors.Transparent;
-        internal string FontType { get; set; } = "Calibri";
-        internal float Size { get; set; } = 12;
-        internal float LineHeight { get; set; } = 1.2f;
-        internal FontWeight FontWeight { get; set; } = FontWeight.Normal;
-        internal bool IsItalic { get; set; } = false;
-        internal bool HasStrikethrough { get; set; } = false;
-        internal bool HasUnderline { get; set; } = false;
+        internal bool HasGlobalStyleApplied { get; private set; }
+        
+        internal string? Color { get; set; }
+        internal string? BackgroundColor { get; set; }
+        internal string? FontType { get; set; }
+        internal float? Size { get; set; }
+        internal float? LineHeight { get; set; }
+        internal FontWeight? FontWeight { get; set; }
+        internal bool? IsItalic { get; set; }
+        internal bool? HasStrikethrough { get; set; }
+        internal bool? HasUnderline { get; set; }
 
-        public static TextStyle Default => new TextStyle();
+        internal string? Key { get; private set; }
+        
+        internal static TextStyle LibraryDefault => new TextStyle
+        {
+            Color = Colors.Black,
+            BackgroundColor = Colors.Transparent,
+            FontType = Fonts.Calibri,
+            Size = 12,
+            LineHeight = 1.2f,
+            FontWeight = Infrastructure.FontWeight.Normal,
+            IsItalic = false,
+            HasStrikethrough = false,
+            HasUnderline = false
+        };
 
-        private string? KeyCache { get; set; }
+        private static TextStyle DefaultTextStyleCache = new TextStyle();
+        public static TextStyle Default => DefaultTextStyleCache;
+
+        internal void ApplyGlobalStyle(TextStyle globalStyle)
+        {
+            if (HasGlobalStyleApplied)
+                return;
+            
+            HasGlobalStyleApplied = true;
+
+            ApplyParentStyle(globalStyle);
+            
+            Key ??= $"{Color}|{BackgroundColor}|{FontType}|{Size}|{LineHeight}|{FontWeight}|{IsItalic}|{HasStrikethrough}|{HasUnderline}";
+        }
         
-        public override string ToString()
+        internal void ApplyParentStyle(TextStyle parentStyle)
         {
-            KeyCache ??= $"{Color}|{BackgroundColor}|{FontType}|{Size}|{LineHeight}|{FontWeight}|{IsItalic}|{HasStrikethrough}|{HasUnderline}";
-            return KeyCache;
+            Color ??= parentStyle.Color;
+            BackgroundColor ??= parentStyle.BackgroundColor;
+            FontType ??= parentStyle.FontType;
+            Size ??= parentStyle.Size;
+            LineHeight ??= parentStyle.LineHeight;
+            FontWeight ??= parentStyle.FontWeight;
+            IsItalic ??= parentStyle.IsItalic;
+            HasStrikethrough ??= parentStyle.HasStrikethrough;
+            HasUnderline ??= parentStyle.HasUnderline;
         }
 
-        internal TextStyle Clone() => (TextStyle)MemberwiseClone();
+        internal TextStyle Clone()
+        {
+            var clone = (TextStyle)MemberwiseClone();
+            clone.HasGlobalStyleApplied = false;
+            return clone;
+        }
     }
 }