Browse Source

Fixed text rendering issues

MarcinZiabek 3 years ago
parent
commit
5402e5fedc

+ 19 - 26
QuestPDF.Examples/TextBenchmark.cs

@@ -23,7 +23,7 @@ namespace QuestPDF.Examples
                 .PageSize(PageSizes.A4)
                 .PageSize(PageSizes.A4)
                 .ProducePdf()
                 .ProducePdf()
                 .ShowResults()
                 .ShowResults()
-                .Render(x => ComposeBook(x, chapters));
+                .RenderDocument(x => ComposeBook(x, chapters));
         }
         }
         
         
         [Test]
         [Test]
@@ -42,8 +42,8 @@ namespace QuestPDF.Examples
                 RenderingTest
                 RenderingTest
                     .Create()
                     .Create()
                     .PageSize(PageSizes.A4)
                     .PageSize(PageSizes.A4)
-                        .ProducePdf()
-                    .Render(x => ComposeBook(x, chapters));
+                    .ProducePdf()
+                    .RenderDocument(x => ComposeBook(x, chapters));
             }
             }
 
 
             IEnumerable<float> PerformTest(int attempts)
             IEnumerable<float> PerformTest(int attempts)
@@ -102,37 +102,30 @@ namespace QuestPDF.Examples
             }
             }
         }
         }
         
         
-        private void ComposeBook(IContainer container, ICollection<BookChapter> chapters)
+        private void ComposeBook(IDocumentContainer container, ICollection<BookChapter> chapters)
         {
         {
             var subtitleStyle = TextStyle.Default.Size(24).SemiBold().Color(Colors.Blue.Medium);
             var subtitleStyle = TextStyle.Default.Size(24).SemiBold().Color(Colors.Blue.Medium);
             var normalStyle = TextStyle.Default.Size(14);
             var normalStyle = TextStyle.Default.Size(14);
-            
-            ComposePage(container);
 
 
-            void ComposePage(IContainer container)
+            container.Page(page =>
             {
             {
-                container
-                    .Padding(50)
-                    .Decoration(decoration =>
-                    {
-                        decoration
-                            .Content()
-                            .Column(column =>
-                            {
-                                column.Item().Element(Title);
-                                column.Item().PageBreak();
-                                column.Item().Element(TableOfContents);
-                                column.Item().PageBreak();
+                page.Margin(50);
+                
+                page.Content().Column(column =>
+                {
+                    column.Item().Element(Title);
+                    column.Item().PageBreak();
+                    column.Item().Element(TableOfContents);
+                    column.Item().PageBreak();
 
 
-                                Chapters(column);
+                    Chapters(column);
 
 
-                                column.Item().Element(Acknowledgements);
-                            });
+                    column.Item().Element(Acknowledgements);
+                });
+                
+                page.Footer().Element(Footer);
+            });
 
 
-                        decoration.After().Element(Footer);
-                    });
-            }
-            
             void Title(IContainer container)
             void Title(IContainer container)
             {
             {
                 container
                 container

+ 55 - 4
QuestPDF.Examples/TextExamples.cs

@@ -31,7 +31,8 @@ namespace QuestPDF.Examples
                         {
                         {
                             text.DefaultTextStyle(TextStyle.Default.Size(20));
                             text.DefaultTextStyle(TextStyle.Default.Size(20));
                             text.Span("This is a normal text, followed by an ");
                             text.Span("This is a normal text, followed by an ");
-                            text.Span("underlined red text.", TextStyle.Default.Size(20).Color(Colors.Red.Medium).Underline());
+                            text.Span("underlined red text", TextStyle.Default.Color(Colors.Red.Medium).Underline());
+                            text.Span(".");
                         });
                         });
                 });
                 });
         }
         }
@@ -189,7 +190,7 @@ namespace QuestPDF.Examples
                             text.EmptyLine();
                             text.EmptyLine();
 
 
                             text.Span("The new text element also supports injecting custom content between words: ");
                             text.Span("The new text element also supports injecting custom content between words: ");
-                            text.Element().PaddingBottom(-10).Height(16).Width(32).Image(Placeholders.Image);
+                            text.Element().PaddingBottom(-4).Height(16).Width(32).Image(Placeholders.Image);
                             text.Span(".");
                             text.Span(".");
 
 
                             text.EmptyLine();
                             text.EmptyLine();
@@ -246,7 +247,7 @@ namespace QuestPDF.Examples
                             text.Span("and this is slightly bigger text.", TextStyle.Default.Size(16));
                             text.Span("and this is slightly bigger text.", TextStyle.Default.Size(16));
                             
                             
                             text.Span("The new text element also supports injecting custom content between words: ");
                             text.Span("The new text element also supports injecting custom content between words: ");
-                            text.Element().PaddingBottom(-10).Height(16).Width(32).Image(Placeholders.Image);
+                            text.Element().PaddingBottom(-4).Height(16).Width(32).Image(Placeholders.Image);
                             text.Span(".");
                             text.Span(".");
                             
                             
                             text.EmptyLine();
                             text.EmptyLine();
@@ -288,7 +289,57 @@ namespace QuestPDF.Examples
 
 
                         page.Content().Text( 
                         page.Content().Text( 
                             "This is a specially crafted sentence with a specially chosen length for demonstration of the bug that occurs ;;;;;. ",
                             "This is a specially crafted sentence with a specially chosen length for demonstration of the bug that occurs ;;;;;. ",
-                            TextStyle.Default.Size(11));
+                            TextStyle.Default.Size(11).BackgroundColor(Colors.Red.Lighten3));
+                    });
+                });
+        }
+        
+        [Test]
+        public void EmptyText()
+        {
+            // issue 135
+            
+            RenderingTest
+                .Create()
+                .ProduceImages()
+                .ShowResults()
+                .RenderDocument(container =>
+                {
+                    container.Page(page =>
+                    {
+                        page.Margin(50);
+                        page.Background(Colors.White);
+
+                        page.Size(PageSizes.A4);
+
+                        page.Content().Text( 
+                            "         ",
+                            TextStyle.Default.Size(11).BackgroundColor(Colors.Red.Lighten3));
+                    });
+                });
+        }
+        
+        [Test]
+        public void Whitespaces()
+        {
+            // issue 135
+            
+            RenderingTest
+                .Create()
+                .ProduceImages()
+                .ShowResults()
+                .RenderDocument(container =>
+                {
+                    container.Page(page =>
+                    {
+                        page.Margin(50);
+                        page.Background(Colors.White);
+
+                        page.Size(PageSizes.A4);
+
+                        page.Content().Text( 
+                            "     x     ",
+                            TextStyle.Default.Size(11).BackgroundColor(Colors.Red.Lighten3));
                     });
                     });
                 });
                 });
         }
         }

+ 1 - 1
QuestPDF/Elements/Text/Calculation/TextMeasurementRequest.cs

@@ -9,6 +9,6 @@ namespace QuestPDF.Elements.Text.Calculation
         
         
         public int StartIndex { get; set; }
         public int StartIndex { get; set; }
         public float AvailableWidth { get; set; }
         public float AvailableWidth { get; set; }
-        public bool IsFirstLineElement { get; set; }
+        public bool IsFirstElementInLine { get; set; }
     }
     }
 }
 }

+ 1 - 1
QuestPDF/Elements/Text/Calculation/TextMeasurementResult.cs

@@ -17,6 +17,6 @@ namespace QuestPDF.Elements.Text.Calculation
         public int NextIndex { get; set; }
         public int NextIndex { get; set; }
         public int TotalIndex { get; set; }
         public int TotalIndex { get; set; }
 
 
-        public bool IsLast => EndIndex == TotalIndex;
+        public bool IsLast => NextIndex == EndIndex;
     }
     }
 }
 }

+ 40 - 24
QuestPDF/Elements/Text/Items/TextBlockSpan.cs

@@ -33,13 +33,15 @@ namespace QuestPDF.Elements.Text.Items
 
 
             var startIndex = request.StartIndex;
             var startIndex = request.StartIndex;
             
             
-            if (request.IsFirstLineElement)
+            // if the element is the first one within the line,
+            // ignore leading spaces
+            if (request.IsFirstElementInLine)
             {
             {
-                while (startIndex + 1 < Text.Length && Text[startIndex] == space)
+                while (startIndex < Text.Length && Text[startIndex] == space)
                     startIndex++;
                     startIndex++;
             }
             }
-
-            if (Text.Length == 0)
+            
+            if (Text.Length == 0 || startIndex == Text.Length)
             {
             {
                 return new TextMeasurementResult
                 return new TextMeasurementResult
                 {
                 {
@@ -58,37 +60,26 @@ namespace QuestPDF.Elements.Text.Items
 
 
             if (textLength <= 0)
             if (textLength <= 0)
                 return null;
                 return null;
-
-            if (textLength < text.Length && text[textLength] == space)
-                textLength++;
-            
+  
             // break text only on spaces
             // break text only on spaces
-            if (textLength < text.Length)
-            {
-                var lastSpaceIndex = text.Slice(0, textLength).LastIndexOf(space) - 1;
+            var wrappedTextLength = WrapText(text, textLength, request.IsFirstElementInLine);
 
 
-                if (lastSpaceIndex <= 0)
-                {
-                    if (!request.IsFirstLineElement)
-                        return null;
-                }
-                else
-                {
-                    textLength = lastSpaceIndex + 1;
-                }
-            }
+            if (wrappedTextLength == null)
+                return null;
+
+            textLength = wrappedTextLength.Value;
 
 
             text = text.Slice(0, textLength);
             text = text.Slice(0, textLength);
 
 
             var endIndex = startIndex + textLength;
             var endIndex = startIndex + textLength;
             var nextIndex = endIndex;
             var nextIndex = endIndex;
 
 
-            while (nextIndex + 1 < Text.Length && Text[nextIndex] == space)
+            // when breaking text, omit spaces at the end of the line
+            while (nextIndex < Text.Length && Text[nextIndex] == space)
                 nextIndex++;
                 nextIndex++;
             
             
             // measure final text
             // measure final text
-            var finalText = text.TrimEnd();
-            var width = paint.MeasureText(finalText);
+            var width = paint.MeasureText(text);
             
             
             return new TextMeasurementResult
             return new TextMeasurementResult
             {
             {
@@ -104,6 +95,31 @@ namespace QuestPDF.Elements.Text.Items
                 NextIndex = nextIndex,
                 NextIndex = nextIndex,
                 TotalIndex = Text.Length
                 TotalIndex = Text.Length
             };
             };
+
+            static int? WrapText(ReadOnlySpan<char> text, int textLength, bool isFirstElementInLine)
+            {
+                // textLength - length of the part of the text that fits in available width (creating a line)
+                
+                // entire text fits, no need to wrap
+                if (textLength == text.Length)
+                    return textLength;
+
+                // current line ends at word, next character is space, perfect place to wrap
+                if (text[textLength - 1] != space && text[textLength] == space)
+                    return textLength;
+                
+                // find last space within the available text to wrap
+                var lastSpaceIndex = text.Slice(0, textLength).LastIndexOf(space);
+
+                // text contains space that can be used to wrap
+                if (lastSpaceIndex > 0)
+                    return lastSpaceIndex;
+                
+                // there is no available space to wrap text
+                // if the item is first within the line, perform safe mode and chop the word
+                // otherwise, move the item into the next line
+                return isFirstElementInLine ? textLength : null;
+            }
         }
         }
         
         
         public virtual void Draw(TextDrawingRequest request)
         public virtual void Draw(TextDrawingRequest request)

+ 1 - 1
QuestPDF/Elements/Text/TextBlock.cs

@@ -170,7 +170,7 @@ namespace QuestPDF.Elements.Text
                         
                         
                         StartIndex = currentItemIndex,
                         StartIndex = currentItemIndex,
                         AvailableWidth = availableWidth - currentWidth,
                         AvailableWidth = availableWidth - currentWidth,
-                        IsFirstLineElement = !currentLineElements.Any()
+                        IsFirstElementInLine = !currentLineElements.Any()
                     };
                     };
                 
                 
                     var measurementResponse = currentElement.Measure(measurementRequest);
                     var measurementResponse = currentElement.Measure(measurementRequest);