Introduced new line breaking algorithm: break anywhere
This commit is contained in:
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<char> 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<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;
|
||||
// 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)
|
||||
|
||||
@@ -187,7 +187,7 @@ namespace QuestPDF.Elements.Text
|
||||
});
|
||||
|
||||
currentWidth += measurementResponse.Width;
|
||||
currentItemIndex = measurementResponse.EndIndex + 1;
|
||||
currentItemIndex = measurementResponse.NextIndex;
|
||||
|
||||
if (!measurementResponse.IsLast)
|
||||
break;
|
||||
|
||||
@@ -63,6 +63,12 @@ namespace QuestPDF.Fluent
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T BreakAnywhere<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor
|
||||
{
|
||||
descriptor.TextStyle.BreakAnywhere = value;
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
#region Weight
|
||||
|
||||
public static T Weight<T>(this T descriptor, FontWeight weight) where T : TextSpanDescriptor
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user