From 11ff53946d8e3cf0191f41f55f023ca560156140 Mon Sep 17 00:00:00 2001 From: MarcinZiabek Date: Sat, 26 Mar 2022 03:34:31 +0100 Subject: [PATCH] Introduced new line breaking algorithm: break anywhere --- QuestPDF.Examples/TextExamples.cs | 6 ++- .../Text/Calculation/TextMeasurementResult.cs | 1 + QuestPDF/Elements/Text/Items/TextBlockSpan.cs | 54 ++++++++++--------- QuestPDF/Elements/Text/TextBlock.cs | 2 +- .../Fluent/TextSpanDescriptorExtensions.cs | 6 +++ QuestPDF/Fluent/TextStyleExtensions.cs | 5 ++ QuestPDF/Infrastructure/TextStyle.cs | 6 ++- 7 files changed, 52 insertions(+), 28 deletions(-) diff --git a/QuestPDF.Examples/TextExamples.cs b/QuestPDF.Examples/TextExamples.cs index 3381b6f..778aa85 100644 --- a/QuestPDF.Examples/TextExamples.cs +++ b/QuestPDF.Examples/TextExamples.cs @@ -394,8 +394,10 @@ namespace QuestPDF.Examples column.Item().Text(text => { - text.DefaultTextStyle(x => x.BackgroundColor(Colors.Red.Lighten3)); - text.Span(" " + Placeholders.LoremIpsum() + " 0123456789012345678901234567890123456789012345678901234567890123456789 ").FontSize(24); + text.DefaultTextStyle(x => x.BackgroundColor(Colors.Red.Lighten3).FontSize(24)); + + text.Span(" " + Placeholders.LoremIpsum()); + text.Span(" 0123456789012345678901234567890123456789012345678901234567890123456789 ").BreakAnywhere(); }); }); }); diff --git a/QuestPDF/Elements/Text/Calculation/TextMeasurementResult.cs b/QuestPDF/Elements/Text/Calculation/TextMeasurementResult.cs index a831ae4..6cded5b 100644 --- a/QuestPDF/Elements/Text/Calculation/TextMeasurementResult.cs +++ b/QuestPDF/Elements/Text/Calculation/TextMeasurementResult.cs @@ -14,6 +14,7 @@ namespace QuestPDF.Elements.Text.Calculation public int StartIndex { get; set; } public int EndIndex { get; set; } + public int NextIndex { get; set; } public int TotalIndex { get; set; } public bool IsLast => EndIndex == TotalIndex; diff --git a/QuestPDF/Elements/Text/Items/TextBlockSpan.cs b/QuestPDF/Elements/Text/Items/TextBlockSpan.cs index 69d0ce3..7fed20d 100644 --- a/QuestPDF/Elements/Text/Items/TextBlockSpan.cs +++ b/QuestPDF/Elements/Text/Items/TextBlockSpan.cs @@ -9,6 +9,8 @@ namespace QuestPDF.Elements.Text.Items { internal class TextBlockSpan : ITextBlockItem { + private const char Space = ' '; + public string Text { get; set; } public TextStyle Style { get; set; } = new TextStyle(); @@ -26,8 +28,6 @@ namespace QuestPDF.Elements.Text.Items internal TextMeasurementResult? MeasureWithoutCache(TextMeasurementRequest request) { - const char space = ' '; - var paint = Style.ToPaint(); var fontMetrics = Style.ToFontMetrics(); @@ -37,7 +37,7 @@ namespace QuestPDF.Elements.Text.Items // ignore leading spaces if (!request.IsFirstElementInBlock && request.IsFirstElementInLine) { - while (startIndex < Text.Length && Text[startIndex] == space) + while (startIndex < Text.Length && Text[startIndex] == Space) startIndex++; } @@ -67,7 +67,7 @@ namespace QuestPDF.Elements.Text.Items if (wrappedTextLength == null) return null; - textLength = wrappedTextLength.Value; + textLength = wrappedTextLength.Value.fragmentLength; text = text.Slice(0, textLength); @@ -87,33 +87,39 @@ namespace QuestPDF.Elements.Text.Items StartIndex = startIndex, EndIndex = endIndex, + NextIndex = startIndex + wrappedTextLength.Value.nextIndex, TotalIndex = Text.Length }; - - static int? WrapText(ReadOnlySpan text, int textLength, bool isFirstElementInLine) - { - // textLength - length of the part of the text that fits in available width (creating a line) + } + + // TODO: consider introduce text wrapping abstraction (basic, english-like, asian-like) + private (int fragmentLength, int nextIndex)? WrapText(ReadOnlySpan 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; + // entire text fits, no need to wrap + if (textLength == text.Length) + return (textLength, textLength + 1); - // current line ends at word, next character is space, perfect place to wrap - if (text[textLength - 1] != space && text[textLength] == space) - return textLength; + // breaking anywhere + if (Style.BreakAnywhere ?? false) + return (textLength, textLength); - // find last space within the available text to wrap - var lastSpaceIndex = text.Slice(0, textLength).LastIndexOf(space); + // current line ends at word, next character is space, perfect place to wrap + if (text[textLength - 1] != Space && text[textLength] == Space) + return (textLength, textLength + 1); + + // 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; + // text contains space that can be used to wrap + if (lastSpaceIndex > 0) + return (lastSpaceIndex, lastSpaceIndex + 1); - // 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; - } + // 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, textLength + 1) : null; } public virtual void Draw(TextDrawingRequest request) diff --git a/QuestPDF/Elements/Text/TextBlock.cs b/QuestPDF/Elements/Text/TextBlock.cs index d355132..5028f9e 100644 --- a/QuestPDF/Elements/Text/TextBlock.cs +++ b/QuestPDF/Elements/Text/TextBlock.cs @@ -187,7 +187,7 @@ namespace QuestPDF.Elements.Text }); currentWidth += measurementResponse.Width; - currentItemIndex = measurementResponse.EndIndex + 1; + currentItemIndex = measurementResponse.NextIndex; if (!measurementResponse.IsLast) break; diff --git a/QuestPDF/Fluent/TextSpanDescriptorExtensions.cs b/QuestPDF/Fluent/TextSpanDescriptorExtensions.cs index 9af8e9a..0421b5a 100644 --- a/QuestPDF/Fluent/TextSpanDescriptorExtensions.cs +++ b/QuestPDF/Fluent/TextSpanDescriptorExtensions.cs @@ -63,6 +63,12 @@ namespace QuestPDF.Fluent return descriptor; } + public static T BreakAnywhere(this T descriptor, bool value = true) where T : TextSpanDescriptor + { + descriptor.TextStyle.BreakAnywhere = value; + return descriptor; + } + #region Weight public static T Weight(this T descriptor, FontWeight weight) where T : TextSpanDescriptor diff --git a/QuestPDF/Fluent/TextStyleExtensions.cs b/QuestPDF/Fluent/TextStyleExtensions.cs index 94e4351..e115f01 100644 --- a/QuestPDF/Fluent/TextStyleExtensions.cs +++ b/QuestPDF/Fluent/TextStyleExtensions.cs @@ -71,6 +71,11 @@ namespace QuestPDF.Fluent { return style.Mutate(x => x.HasUnderline = value); } + + public static TextStyle BreakAnywhere(this TextStyle style, bool value = true) + { + return style.Mutate(x => x.BreakAnywhere = value); + } #region Weight diff --git a/QuestPDF/Infrastructure/TextStyle.cs b/QuestPDF/Infrastructure/TextStyle.cs index 4556648..bdc216d 100644 --- a/QuestPDF/Infrastructure/TextStyle.cs +++ b/QuestPDF/Infrastructure/TextStyle.cs @@ -16,6 +16,7 @@ namespace QuestPDF.Infrastructure internal bool? IsItalic { get; set; } internal bool? HasStrikethrough { get; set; } internal bool? HasUnderline { get; set; } + internal bool? BreakAnywhere { get; set; } internal object PaintKey { get; private set; } internal object FontMetricsKey { get; private set; } @@ -30,7 +31,8 @@ namespace QuestPDF.Infrastructure FontWeight = Infrastructure.FontWeight.Normal, IsItalic = false, HasStrikethrough = false, - HasUnderline = false + HasUnderline = false, + BreakAnywhere = false }; public static TextStyle Default => new TextStyle(); @@ -58,6 +60,7 @@ namespace QuestPDF.Infrastructure IsItalic ??= parentStyle.IsItalic; HasStrikethrough ??= parentStyle.HasStrikethrough; HasUnderline ??= parentStyle.HasUnderline; + BreakAnywhere ??= parentStyle.BreakAnywhere; } internal void OverrideStyle(TextStyle parentStyle) @@ -71,6 +74,7 @@ namespace QuestPDF.Infrastructure IsItalic = parentStyle.IsItalic ?? IsItalic; HasStrikethrough = parentStyle.HasStrikethrough ?? HasStrikethrough; HasUnderline = parentStyle.HasUnderline ?? HasUnderline; + BreakAnywhere = parentStyle.BreakAnywhere ?? BreakAnywhere; } internal TextStyle Clone()