diff --git a/QuestPDF/Elements/Text/Calculation/TextMeasurementResult.cs b/QuestPDF/Elements/Text/Calculation/TextMeasurementResult.cs index 4643763..13b7841 100644 --- a/QuestPDF/Elements/Text/Calculation/TextMeasurementResult.cs +++ b/QuestPDF/Elements/Text/Calculation/TextMeasurementResult.cs @@ -19,5 +19,6 @@ namespace QuestPDF.Elements.Text.Calculation public int TotalIndex { get; set; } public bool IsLast => EndIndex == TotalIndex; + public bool IsNewLine { get; set; } } } \ No newline at end of file diff --git a/QuestPDF/Elements/Text/Items/TextBlockSpan.cs b/QuestPDF/Elements/Text/Items/TextBlockSpan.cs index 033931e..e40c2cf 100644 --- a/QuestPDF/Elements/Text/Items/TextBlockSpan.cs +++ b/QuestPDF/Elements/Text/Items/TextBlockSpan.cs @@ -39,18 +39,34 @@ namespace QuestPDF.Elements.Text.Items var paint = Style.ToPaint(); var fontMetrics = Style.ToFontMetrics(); - var spaceCodepoint = paint.ToFont().Typeface.GetGlyphs(" ")[0]; + // if the element is the first one within the line, + // ignore leading spaces and new lines + var spaceCodepoint = paint.ToFont().Typeface.GetGlyph(' '); + var newLineCodepoint = paint.ToFont().Typeface.GetGlyph('\n'); + var returnCodepoint = paint.ToFont().Typeface.GetGlyph('\r'); + var startIndex = request.StartIndex; - // if the element is the first one within the line, - // ignore leading spaces if (!request.IsFirstElementInBlock && request.IsFirstElementInLine) { - while (startIndex < TextShapingResult.Glyphs.Length && Text[startIndex] == spaceCodepoint) + while (startIndex < TextShapingResult.Glyphs.Length && (Text[startIndex] == spaceCodepoint || Text[startIndex] == newLineCodepoint || Text[startIndex] == returnCodepoint)) startIndex++; } + // calculate max index (new new line) + var newLineIndex = startIndex; + + while (newLineIndex < TextShapingResult.Glyphs.Length) + { + var glyphCodepoint = TextShapingResult.Glyphs[newLineIndex].Codepoint; + + if (glyphCodepoint == newLineCodepoint || glyphCodepoint == returnCodepoint) + break; + + newLineIndex++; + } + if (TextShapingResult.Glyphs.Length == 0 || startIndex == TextShapingResult.Glyphs.Length) { return new TextMeasurementResult @@ -68,6 +84,8 @@ namespace QuestPDF.Elements.Text.Items if (endIndex < startIndex) return null; + + endIndex = Math.Min(endIndex, newLineIndex); // break text only on spaces var wrappedText = WrapText(startIndex, endIndex, request.IsFirstElementInLine); @@ -90,7 +108,9 @@ namespace QuestPDF.Elements.Text.Items StartIndex = startIndex, EndIndex = wrappedText.Value.endIndex, NextIndex = wrappedText.Value.nextIndex, - TotalIndex = TextShapingResult.Glyphs.Length - 1 + TotalIndex = TextShapingResult.Glyphs.Length - 1, + + IsNewLine = endIndex == newLineIndex }; } diff --git a/QuestPDF/Elements/Text/TextBlock.cs b/QuestPDF/Elements/Text/TextBlock.cs index 6420a7d..7d8a7bf 100644 --- a/QuestPDF/Elements/Text/TextBlock.cs +++ b/QuestPDF/Elements/Text/TextBlock.cs @@ -11,7 +11,8 @@ namespace QuestPDF.Elements.Text internal class TextBlock : Element, IStateResettable { public HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left; - public List Items { get; set; } = new List(); + public List Items { get; set; } = new(); + public float ParagraphSpacing { get; set; } = 0; public string Text => string.Join(" ", Items.Where(x => x is TextBlockSpan).Cast().Select(x => x.Text)); @@ -110,6 +111,12 @@ namespace QuestPDF.Elements.Text Canvas.Translate(new Position(0, line.LineHeight)); heightOffset += line.LineHeight; + + if (line.Elements.Last().Measurement.IsNewLine) + { + heightOffset += ParagraphSpacing; + Canvas.Translate(new Position(0, ParagraphSpacing)); + } } Canvas.Translate(new Position(0, -heightOffset)); diff --git a/QuestPDF/Fluent/TextExtensions.cs b/QuestPDF/Fluent/TextExtensions.cs index d0e421d..8839280 100644 --- a/QuestPDF/Fluent/TextExtensions.cs +++ b/QuestPDF/Fluent/TextExtensions.cs @@ -40,10 +40,9 @@ namespace QuestPDF.Fluent public class TextDescriptor { - private ICollection TextBlocks { get; } = new List(); + internal TextBlock TextBlock = new(); private TextStyle DefaultStyle { get; set; } = TextStyle.Default; internal HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left; - private float Spacing { get; set; } = 0f; public void DefaultTextStyle(TextStyle style) { @@ -72,17 +71,9 @@ namespace QuestPDF.Fluent public void ParagraphSpacing(float value, Unit unit = Unit.Point) { - Spacing = value.ToPoints(unit); + TextBlock.ParagraphSpacing = value.ToPoints(unit); } - private void AddItemToLastTextBlock(ITextBlockItem item) - { - if (!TextBlocks.Any()) - TextBlocks.Add(new TextBlock()); - - TextBlocks.Last().Items.Add(item); - } - [Obsolete("This element has been renamed since version 2022.3. Please use the overload that returns a TextSpanDescriptor object which allows to specify text style.")] public void Span(string? text, TextStyle style) { @@ -96,28 +87,13 @@ namespace QuestPDF.Fluent if (text == null) return descriptor; - - var items = text - .Replace("\r", string.Empty) - .Split(new[] { '\n' }, StringSplitOptions.None) - .Select(x => new TextBlockSpan - { - Text = x, - Style = style - }) - .ToList(); - - AddItemToLastTextBlock(items.First()); - - items - .Skip(1) - .Select(x => new TextBlock - { - Items = new List { x } - }) - .ToList() - .ForEach(TextBlocks.Add); - + + TextBlock.Items.Add(new TextBlockSpan + { + Text = text, + Style = style + }); + return descriptor; } @@ -137,7 +113,7 @@ namespace QuestPDF.Fluent var style = DefaultStyle.Clone(); var descriptor = new TextPageNumberDescriptor(style); - AddItemToLastTextBlock(new TextBlockPageNumber + TextBlock.Items.Add(new TextBlockPageNumber { Source = context => descriptor.FormatFunction(pageNumber(context)), Style = style @@ -193,7 +169,7 @@ namespace QuestPDF.Fluent if (IsNullOrEmpty(text)) return descriptor; - AddItemToLastTextBlock(new TextBlockSectionLink + TextBlock.Items.Add(new TextBlockSectionLink { Style = style, Text = text, @@ -220,7 +196,7 @@ namespace QuestPDF.Fluent if (IsNullOrEmpty(text)) return descriptor; - AddItemToLastTextBlock(new TextBlockHyperlink + TextBlock.Items.Add(new TextBlockHyperlink { Style = style, Text = text, @@ -240,26 +216,13 @@ namespace QuestPDF.Fluent { var container = new Container(); - AddItemToLastTextBlock(new TextBlockElement + TextBlock.Items.Add(new TextBlockElement { Element = container }); return container.AlignBottom().MinimalBox(); } - - internal void Compose(IContainer container) - { - TextBlocks.ToList().ForEach(x => x.Alignment = Alignment); - - container.DefaultTextStyle(DefaultStyle).Column(column => - { - column.Spacing(Spacing); - - foreach (var textBlock in TextBlocks) - column.Item().Element(textBlock); - }); - } } public static class TextExtensions @@ -272,7 +235,7 @@ namespace QuestPDF.Fluent descriptor.Alignment = alignment.Horizontal; content?.Invoke(descriptor); - descriptor.Compose(element); + element.Element(descriptor.TextBlock); } [Obsolete("This element has been renamed since version 2022.3. Please use the overload that returns a TextSpanDescriptor object which allows to specify text style.")]