Implemented text rendering benchmark
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
[TestFixture]
|
||||
public class BarcodeExamples
|
||||
{
|
||||
[Test]
|
||||
public void Barcode()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 300)
|
||||
.FileName()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Background("#FFF")
|
||||
.Padding(25)
|
||||
.Stack(stack =>
|
||||
{
|
||||
stack.Item().Border(1).Background(Colors.Grey.Lighten3).Padding(5).Text("Barcode Example");
|
||||
stack.Item().Border(1).Padding(5).AlignCenter().Text("*123456789*", TextStyle.Default.FontType("CarolinaBar-Demo-25E2").Size(20));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ namespace QuestPDF.Examples.Engine
|
||||
{
|
||||
private string FileNamePrefix = "test";
|
||||
private Size Size { get; set; }
|
||||
private bool ShowResult { get; set; }
|
||||
private RenderingTestResult ResultType { get; set; } = RenderingTestResult.Images;
|
||||
|
||||
private RenderingTest()
|
||||
@@ -59,26 +60,37 @@ namespace QuestPDF.Examples.Engine
|
||||
ResultType = RenderingTestResult.Images;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RenderingTest ShowResults()
|
||||
{
|
||||
ShowResult = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void Render(Action<IContainer> content)
|
||||
{
|
||||
var container = new Container();
|
||||
content(container);
|
||||
|
||||
var document = new SimpleDocument(container, Size);
|
||||
|
||||
var maxPages = ResultType == RenderingTestResult.Pdf ? 1000 : 10;
|
||||
var document = new SimpleDocument(container, Size, maxPages);
|
||||
|
||||
if (ResultType == RenderingTestResult.Images)
|
||||
{
|
||||
Func<int, string> fileNameSchema = i => $"{FileNamePrefix}-${i}.png";
|
||||
document.GenerateImages(fileNameSchema);
|
||||
Process.Start("explorer", fileNameSchema(0));
|
||||
|
||||
if (ShowResult)
|
||||
Process.Start("explorer", fileNameSchema(0));
|
||||
}
|
||||
|
||||
if (ResultType == RenderingTestResult.Pdf)
|
||||
{
|
||||
var fileName = $"{FileNamePrefix}.pdf";
|
||||
document.GeneratePdf(fileName);
|
||||
Process.Start("explorer", fileName);
|
||||
|
||||
if (ShowResult)
|
||||
Process.Start("explorer", fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,11 +12,13 @@ namespace QuestPDF.Examples.Engine
|
||||
|
||||
private IContainer Container { get; }
|
||||
private Size Size { get; }
|
||||
private int MaxPages { get; }
|
||||
|
||||
public SimpleDocument(IContainer container, Size size)
|
||||
public SimpleDocument(IContainer container, Size size, int maxPages)
|
||||
{
|
||||
Container = container;
|
||||
Size = size;
|
||||
MaxPages = maxPages;
|
||||
}
|
||||
|
||||
public DocumentMetadata GetMetadata()
|
||||
@@ -24,7 +26,7 @@ namespace QuestPDF.Examples.Engine
|
||||
return new DocumentMetadata()
|
||||
{
|
||||
RasterDpi = PageSizes.PointsPerInch * ImageScalingFactor,
|
||||
DocumentLayoutExceptionThreshold = 10
|
||||
DocumentLayoutExceptionThreshold = MaxPages
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="LibreBarcode39-Regular.ttf">
|
||||
<None Update="quo-vadis.txt">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class TextBenchmark
|
||||
{
|
||||
[Test]
|
||||
public void Benchmark()
|
||||
{
|
||||
var subtitleStyle = TextStyle.Default.Size(24).SemiBold().Color(Colors.Blue.Medium);
|
||||
var normalStyle = TextStyle.Default.Size(14);
|
||||
|
||||
var chapters = GetChapters().ToList();
|
||||
|
||||
var results = PerformTest(16).ToList();
|
||||
|
||||
Console.WriteLine($"Min: {results.Min():F}");
|
||||
Console.WriteLine($"Max: {results.Max():F}");
|
||||
Console.WriteLine($"Avg: {results.Average():F}");
|
||||
|
||||
IEnumerable<(string title, string content)> GetChapters()
|
||||
{
|
||||
var book = File.ReadAllLines("quo-vadis.txt");
|
||||
|
||||
var chapterPointers = book
|
||||
.Select((line, index) => new
|
||||
{
|
||||
LineNumber = index,
|
||||
Text = line
|
||||
})
|
||||
.Where(x => x.Text.Length < 50 && x.Text.Contains("Rozdział") || x.Text.Contains("-----"))
|
||||
.Select(x => x.LineNumber)
|
||||
.ToList();
|
||||
|
||||
foreach (var index in Enumerable.Range(0, chapterPointers.Count - 1))
|
||||
{
|
||||
var chapter = chapterPointers[index];
|
||||
|
||||
var title = book[chapter];
|
||||
|
||||
var lineFrom = chapterPointers[index];
|
||||
var lineTo = chapterPointers[index + 1] - 1;
|
||||
|
||||
var lines = book.Skip(lineFrom + 1).Take(lineTo - lineFrom);
|
||||
var content = string.Join(Environment.NewLine, lines);
|
||||
|
||||
yield return (title, content);
|
||||
}
|
||||
}
|
||||
|
||||
void GenerateDocument()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.FileName()
|
||||
.ProducePdf()
|
||||
.Render(ComposePage);
|
||||
}
|
||||
|
||||
IEnumerable<float> PerformTest(int attempts)
|
||||
{
|
||||
foreach (var i in Enumerable.Range(0, attempts))
|
||||
{
|
||||
var timer = new Stopwatch();
|
||||
|
||||
timer.Start();
|
||||
GenerateDocument();
|
||||
timer.Stop();
|
||||
|
||||
Console.WriteLine($"Attempt {i}: {timer.ElapsedMilliseconds:F}");
|
||||
yield return timer.ElapsedMilliseconds;
|
||||
}
|
||||
}
|
||||
|
||||
void ComposePage(IContainer container)
|
||||
{
|
||||
container
|
||||
.Padding(50)
|
||||
.Decoration(decoration =>
|
||||
{
|
||||
decoration
|
||||
.Content()
|
||||
.Stack(stack =>
|
||||
{
|
||||
stack.Item().Element(Title);
|
||||
stack.Item().PageBreak();
|
||||
stack.Item().Element(TableOfContents);
|
||||
stack.Item().PageBreak();
|
||||
|
||||
Chapters(stack);
|
||||
});
|
||||
|
||||
decoration.Footer().Element(Footer);
|
||||
});
|
||||
}
|
||||
|
||||
void Title(IContainer container)
|
||||
{
|
||||
container
|
||||
.Extend()
|
||||
.PaddingBottom(200)
|
||||
.AlignBottom()
|
||||
.Stack(stack =>
|
||||
{
|
||||
stack.Item().Text("Quo Vadis", TextStyle.Default.Size(72).Bold().Color(Colors.Blue.Darken2));
|
||||
stack.Item().Text("Henryk Sienkiewicz", TextStyle.Default.Size(24).Color(Colors.Grey.Darken2));
|
||||
});
|
||||
}
|
||||
|
||||
void TableOfContents(IContainer container)
|
||||
{
|
||||
container.Stack(stack =>
|
||||
{
|
||||
stack.Item().Text("Table of contents", subtitleStyle);
|
||||
stack.Item().PaddingTop(10).PaddingBottom(50).BorderBottom(1).BorderColor(Colors.Grey.Lighten2).ExtendHorizontal();
|
||||
|
||||
foreach (var chapter in chapters)
|
||||
{
|
||||
stack.Item().InternalLink(chapter.title).Row(row =>
|
||||
{
|
||||
row.RelativeColumn().Text(chapter.title);
|
||||
row.ConstantColumn(100).AlignRight().Text(text => text.PageNumberOfLocation(chapter.title, normalStyle));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Chapters(StackDescriptor stack)
|
||||
{
|
||||
foreach (var chapter in chapters)
|
||||
{
|
||||
stack.Item().Element(container => Chapter(container, chapter.title, chapter.content));
|
||||
}
|
||||
}
|
||||
|
||||
void Chapter(IContainer container, string title, string content)
|
||||
{
|
||||
container.Stack(stack =>
|
||||
{
|
||||
stack.Item().Location(title).Text(title, subtitleStyle);
|
||||
stack.Item().PaddingTop(10).PaddingBottom(50).BorderBottom(1).BorderColor(Colors.Grey.Lighten2).ExtendHorizontal();
|
||||
|
||||
stack.Item().Text(text =>
|
||||
{
|
||||
text.ParagraphSpacing(10);
|
||||
text.Span(content, normalStyle);
|
||||
});
|
||||
|
||||
stack.Item().PageBreak();
|
||||
});
|
||||
}
|
||||
|
||||
void Footer(IContainer container)
|
||||
{
|
||||
container
|
||||
.AlignCenter()
|
||||
.Text(text =>
|
||||
{
|
||||
text.CurrentPageNumber();
|
||||
text.Span(" / ");
|
||||
text.TotalPages();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
@@ -30,9 +32,11 @@ namespace QuestPDF.Examples
|
||||
text.Span("Then something bigger. ", TextStyle.Default.Size(28).Color(Colors.DeepOrange.Darken2).BackgroundColor(Colors.Yellow.Lighten3).Underlined());
|
||||
text.Span("And tiny teeny-tiny. ", TextStyle.Default.Size(6));
|
||||
text.Span("Stroked text also works fine. ", TextStyle.Default.Size(14).Stroked().BackgroundColor(Colors.Grey.Lighten4));
|
||||
//text.NewLine();
|
||||
text.Span("Is it time for lorem ipsum? ", TextStyle.Default.Size(12).Underlined().BackgroundColor(Colors.Grey.Lighten3));
|
||||
text.Span(Placeholders.LoremIpsum(), TextStyle.Default.Size(12));
|
||||
|
||||
//text.NewLine();
|
||||
text.Span("And now some colors: ", TextStyle.Default.Size(16).Color(Colors.Green.Medium));
|
||||
|
||||
foreach (var i in Enumerable.Range(1, 100))
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# Text optimization
|
||||
|
||||
## Initial state
|
||||
|
||||
Attempts:
|
||||
|
||||
```angular2html
|
||||
Attempt 0: 18389,00
|
||||
Attempt 1: 18627,00
|
||||
Attempt 2: 19745,00
|
||||
Attempt 3: 19690,00
|
||||
Attempt 4: 19032,00
|
||||
Attempt 5: 17773,00
|
||||
Attempt 6: 17570,00
|
||||
Attempt 7: 17691,00
|
||||
Attempt 8: 17642,00
|
||||
Attempt 9: 17945,00
|
||||
Attempt 10: 19876,00
|
||||
Attempt 11: 19731,00
|
||||
Attempt 12: 19158,00
|
||||
Attempt 13: 18004,00
|
||||
Attempt 14: 17734,00
|
||||
Attempt 15: 19352,00
|
||||
```
|
||||
|
||||
Results:
|
||||
|
||||
```
|
||||
Min: 17570,00
|
||||
Max: 19876,00
|
||||
Avg: 18622,44
|
||||
```
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,17 +1,14 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
namespace QuestPDF.Elements.Text
|
||||
{
|
||||
internal class TextLineElement
|
||||
{
|
||||
public TextItem Element { get; set; }
|
||||
public ITextElement Element { get; set; }
|
||||
public TextMeasurementResult Measurement { get; set; }
|
||||
}
|
||||
|
||||
@@ -20,7 +17,7 @@ namespace QuestPDF.Elements
|
||||
public ICollection<TextLineElement> Elements { get; set; }
|
||||
|
||||
public float TextHeight => Elements.Max(x => x.Measurement.Height);
|
||||
public float LineHeight => Elements.Max(x => x.Element.Style.LineHeight * x.Measurement.Height);
|
||||
public float LineHeight => Elements.Max(x => x.Measurement.LineHeight * x.Measurement.Height);
|
||||
|
||||
public float Ascent => Elements.Min(x => x.Measurement.Ascent) - (LineHeight - TextHeight) / 2;
|
||||
public float Descent => Elements.Max(x => x.Measurement.Descent) + (LineHeight - TextHeight) / 2;
|
||||
@@ -31,23 +28,17 @@ namespace QuestPDF.Elements
|
||||
internal class TextBlock : Element, IStateResettable
|
||||
{
|
||||
public HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
|
||||
public List<TextItem> Children { get; set; } = new List<TextItem>();
|
||||
public List<ITextElement> Children { get; set; } = new List<ITextElement>();
|
||||
|
||||
public Queue<TextItem> RenderingQueue { get; set; }
|
||||
public Queue<ITextElement> RenderingQueue { get; set; }
|
||||
public int CurrentElementIndex { get; set; }
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
RenderingQueue = new Queue<TextItem>(Children);
|
||||
RenderingQueue = new Queue<ITextElement>(Children);
|
||||
CurrentElementIndex = 0;
|
||||
}
|
||||
|
||||
internal override void HandleVisitor(Action<Element?> visit)
|
||||
{
|
||||
Children.ForEach(x => x?.HandleVisitor(visit));
|
||||
base.HandleVisitor(visit);
|
||||
}
|
||||
|
||||
|
||||
internal override ISpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
if (!RenderingQueue.Any())
|
||||
@@ -103,6 +94,9 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
var textDrawingRequest = new TextDrawingRequest
|
||||
{
|
||||
Canvas = Canvas,
|
||||
PageContext = PageContext,
|
||||
|
||||
StartIndex = item.Measurement.StartIndex,
|
||||
EndIndex = item.Measurement.EndIndex,
|
||||
|
||||
@@ -144,7 +138,7 @@ namespace QuestPDF.Elements
|
||||
|
||||
public IEnumerable<TextLine> DivideTextItemsIntoLines(float availableWidth, float availableHeight)
|
||||
{
|
||||
var queue = new Queue<TextItem>(RenderingQueue);
|
||||
var queue = new Queue<ITextElement>(RenderingQueue);
|
||||
var currentItemIndex = CurrentElementIndex;
|
||||
var currentHeight = 0f;
|
||||
|
||||
@@ -177,11 +171,14 @@ namespace QuestPDF.Elements
|
||||
|
||||
var measurementRequest = new TextMeasurementRequest
|
||||
{
|
||||
Canvas = Canvas,
|
||||
PageContext = PageContext,
|
||||
|
||||
StartIndex = currentItemIndex,
|
||||
AvailableWidth = availableWidth - currentWidth
|
||||
};
|
||||
|
||||
var measurementResponse = currentElement.MeasureText(measurementRequest);
|
||||
var measurementResponse = currentElement.Measure(measurementRequest);
|
||||
|
||||
if (measurementResponse == null)
|
||||
break;
|
||||
@@ -193,7 +190,7 @@ namespace QuestPDF.Elements
|
||||
});
|
||||
|
||||
currentWidth += measurementResponse.Width;
|
||||
currentItemIndex = measurementResponse.EndIndex + 1;
|
||||
currentItemIndex = measurementResponse.EndIndex;
|
||||
|
||||
if (!measurementResponse.IsLast)
|
||||
break;
|
||||
@@ -1,16 +1,16 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Runtime.InteropServices;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Infrastructure;
|
||||
using SkiaSharp;
|
||||
using Size = QuestPDF.Infrastructure.Size;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
namespace QuestPDF.Elements.Text
|
||||
{
|
||||
internal class TextMeasurementRequest
|
||||
{
|
||||
public ICanvas Canvas { get; set; }
|
||||
public IPageContext PageContext { get; set; }
|
||||
|
||||
public int StartIndex { get; set; }
|
||||
public float AvailableWidth { get; set; }
|
||||
}
|
||||
@@ -19,10 +19,12 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
public float Width { get; set; }
|
||||
public float Height => Math.Abs(Descent) + Math.Abs(Ascent);
|
||||
|
||||
|
||||
public float Ascent { get; set; }
|
||||
public float Descent { get; set; }
|
||||
|
||||
public float LineHeight { get; set; }
|
||||
|
||||
public int StartIndex { get; set; }
|
||||
public int EndIndex { get; set; }
|
||||
|
||||
@@ -32,86 +34,30 @@ namespace QuestPDF.Elements
|
||||
public bool IsLast => EndIndex == TotalIndex;
|
||||
}
|
||||
|
||||
public class TextDrawingRequest
|
||||
internal class TextDrawingRequest
|
||||
{
|
||||
public ICanvas Canvas { get; set; }
|
||||
public IPageContext PageContext { get; set; }
|
||||
|
||||
public int StartIndex { get; set; }
|
||||
public int EndIndex { get; set; }
|
||||
|
||||
public float TotalAscent { get; set; }
|
||||
public Size TextSize { get; set; }
|
||||
}
|
||||
|
||||
internal interface ITextElement
|
||||
{
|
||||
TextMeasurementResult? Measure(TextMeasurementRequest request);
|
||||
void Draw(TextDrawingRequest request);
|
||||
}
|
||||
|
||||
internal class TextItem : Element, IStateResettable
|
||||
internal class TextItem : ITextElement
|
||||
{
|
||||
public string Text { get; set; }
|
||||
|
||||
public TextStyle Style { get; set; } = new TextStyle();
|
||||
internal int PointerIndex { get; set; }
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
PointerIndex = 0;
|
||||
}
|
||||
|
||||
internal override ISpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
return new FullRender(Size.Zero);
|
||||
|
||||
// if (VirtualPointer >= Text.Length)
|
||||
// return new FullRender(Size.Zero);
|
||||
//
|
||||
// var paint = Style.ToPaint();
|
||||
// var metrics = paint.FontMetrics;
|
||||
//
|
||||
// var length = (int)paint.BreakText(Text, availableSpace.Width);
|
||||
// length = VirtualPointer + Text.Substring(VirtualPointer, length).LastIndexOf(" ");
|
||||
//
|
||||
// var textFragment = Text.Substring(VirtualPointer, length);
|
||||
//
|
||||
// var width = paint.MeasureText(textFragment);
|
||||
// var height = Math.Abs(metrics.Descent) + Math.Abs(metrics.Ascent);
|
||||
//
|
||||
// if (availableSpace.Width < width || availableSpace.Height < height)
|
||||
// return new Wrap();
|
||||
//
|
||||
// VirtualPointer += length;
|
||||
//
|
||||
// return new TextRender(width, height)
|
||||
// {
|
||||
// Descent = metrics.Descent,
|
||||
// Ascent = metrics.Ascent
|
||||
// };
|
||||
}
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
internal void Draw(TextDrawingRequest request)
|
||||
{
|
||||
var fontMetrics = Style.ToPaint().FontMetrics;
|
||||
|
||||
var text = Text.Substring(request.StartIndex, request.EndIndex - request.StartIndex);
|
||||
|
||||
Canvas.DrawRectangle(new Position(0, request.TotalAscent), new Size(request.TextSize.Width, request.TextSize.Height), Style.BackgroundColor);
|
||||
Canvas.DrawText(text, Position.Zero, Style);
|
||||
|
||||
// draw underline
|
||||
if (Style.IsUnderlined && fontMetrics.UnderlinePosition.HasValue)
|
||||
DrawLine(fontMetrics.UnderlinePosition.Value, fontMetrics.UnderlineThickness.Value);
|
||||
|
||||
// draw stroke
|
||||
if (Style.IsStroked && fontMetrics.StrikeoutPosition.HasValue)
|
||||
DrawLine(fontMetrics.StrikeoutPosition.Value, fontMetrics.StrikeoutThickness.Value);
|
||||
|
||||
void DrawLine(float offset, float thickness)
|
||||
{
|
||||
Canvas.DrawRectangle(new Position(0, offset - thickness / 2f), new Size(request.TextSize.Width, thickness), Style.Color);
|
||||
}
|
||||
}
|
||||
|
||||
internal TextMeasurementResult? MeasureText(TextMeasurementRequest request)
|
||||
public TextMeasurementResult? Measure(TextMeasurementRequest request)
|
||||
{
|
||||
var paint = Style.ToPaint();
|
||||
|
||||
@@ -129,6 +75,8 @@ namespace QuestPDF.Elements
|
||||
|
||||
if (breakingIndex <= 0)
|
||||
return null;
|
||||
|
||||
breakingIndex += 1;
|
||||
}
|
||||
|
||||
text = text.Substring(0, breakingIndex);
|
||||
@@ -143,10 +91,66 @@ namespace QuestPDF.Elements
|
||||
Ascent = paint.FontMetrics.Ascent,
|
||||
Descent = paint.FontMetrics.Descent,
|
||||
|
||||
LineHeight = Style.LineHeight,
|
||||
|
||||
StartIndex = request.StartIndex,
|
||||
EndIndex = request.StartIndex + breakingIndex,
|
||||
TotalIndex = Text.Length
|
||||
};
|
||||
}
|
||||
|
||||
public void Draw(TextDrawingRequest request)
|
||||
{
|
||||
var fontMetrics = Style.ToPaint().FontMetrics;
|
||||
|
||||
var text = Text.Substring(request.StartIndex, request.EndIndex - request.StartIndex);
|
||||
|
||||
request.Canvas.DrawRectangle(new Position(0, request.TotalAscent), new Size(request.TextSize.Width, request.TextSize.Height), Style.BackgroundColor);
|
||||
request.Canvas.DrawText(text, Position.Zero, Style);
|
||||
|
||||
// draw underline
|
||||
if (Style.IsUnderlined && fontMetrics.UnderlinePosition.HasValue)
|
||||
DrawLine(fontMetrics.UnderlinePosition.Value, fontMetrics.UnderlineThickness.Value);
|
||||
|
||||
// draw stroke
|
||||
if (Style.IsStroked && fontMetrics.StrikeoutPosition.HasValue)
|
||||
DrawLine(fontMetrics.StrikeoutPosition.Value, fontMetrics.StrikeoutThickness.Value);
|
||||
|
||||
void DrawLine(float offset, float thickness)
|
||||
{
|
||||
request.Canvas.DrawRectangle(new Position(0, offset - thickness / 2f), new Size(request.TextSize.Width, thickness), Style.Color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class PageNumberTextItem : ITextElement
|
||||
{
|
||||
public TextStyle Style { get; set; } = new TextStyle();
|
||||
public string SlotName { get; set; }
|
||||
|
||||
public TextMeasurementResult? Measure(TextMeasurementRequest request)
|
||||
{
|
||||
return GetItem(request.PageContext).Measure(request);
|
||||
}
|
||||
|
||||
public void Draw(TextDrawingRequest request)
|
||||
{
|
||||
GetItem(request.PageContext).Draw(request);
|
||||
}
|
||||
|
||||
private TextItem GetItem(IPageContext context)
|
||||
{
|
||||
var pageNumberPlaceholder = 123;
|
||||
|
||||
var pageNumber = context.GetRegisteredLocations().Contains(SlotName)
|
||||
? context.GetLocationPage(SlotName)
|
||||
: pageNumberPlaceholder;
|
||||
|
||||
return new TextItem
|
||||
{
|
||||
Style = Style,
|
||||
Text = pageNumber.ToString()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,40 +2,110 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Elements.Text;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Fluent
|
||||
{
|
||||
public class TextDescriptor
|
||||
{
|
||||
private TextBlock TextBlock { get; }
|
||||
|
||||
internal TextDescriptor(TextBlock textBlock)
|
||||
{
|
||||
TextBlock = textBlock;
|
||||
}
|
||||
|
||||
private ICollection<TextBlock> TextBlocks { get; } = new List<TextBlock>();
|
||||
private HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
|
||||
private float Spacing { get; set; } = 0f;
|
||||
|
||||
public void AlignLeft()
|
||||
{
|
||||
TextBlock.Alignment = HorizontalAlignment.Left;
|
||||
Alignment = HorizontalAlignment.Left;
|
||||
}
|
||||
|
||||
public void AlignCenter()
|
||||
{
|
||||
TextBlock.Alignment = HorizontalAlignment.Center;
|
||||
Alignment = HorizontalAlignment.Center;
|
||||
}
|
||||
|
||||
public void AlignRight()
|
||||
{
|
||||
TextBlock.Alignment = HorizontalAlignment.Right;
|
||||
Alignment = HorizontalAlignment.Right;
|
||||
}
|
||||
|
||||
public void ParagraphSpacing(float value)
|
||||
{
|
||||
Spacing = value;
|
||||
}
|
||||
|
||||
public void Span(string text, TextStyle? style = null)
|
||||
{
|
||||
TextBlock.Children.Add(new TextItem
|
||||
style ??= TextStyle.Default;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
return;
|
||||
|
||||
var items = text
|
||||
.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(x => new TextItem
|
||||
{
|
||||
Text = x,
|
||||
Style = style
|
||||
})
|
||||
.ToList();
|
||||
|
||||
if (!TextBlocks.Any())
|
||||
TextBlocks.Add(new TextBlock());
|
||||
|
||||
TextBlocks.First().Children.Add(items.First());
|
||||
|
||||
items
|
||||
.Skip(1)
|
||||
.Select(x => new TextBlock
|
||||
{
|
||||
Children = new List<ITextElement> { x }
|
||||
})
|
||||
.ToList()
|
||||
.ForEach(TextBlocks.Add);
|
||||
}
|
||||
|
||||
public void NewLine()
|
||||
{
|
||||
Span(Environment.NewLine);
|
||||
}
|
||||
|
||||
private void PageNumber(string slotName, TextStyle? style = null)
|
||||
{
|
||||
style ??= TextStyle.Default;
|
||||
|
||||
if (!TextBlocks.Any())
|
||||
TextBlocks.Add(new TextBlock());
|
||||
|
||||
TextBlocks.First().Children.Add(new PageNumberTextItem()
|
||||
{
|
||||
Text = text,
|
||||
Style = style ?? TextStyle.Default
|
||||
Style = style,
|
||||
SlotName = slotName
|
||||
});
|
||||
}
|
||||
|
||||
public void CurrentPageNumber(TextStyle? style = null)
|
||||
{
|
||||
PageNumber(PageContext.CurrentPageSlot, style);
|
||||
}
|
||||
|
||||
public void TotalPages(TextStyle? style = null)
|
||||
{
|
||||
PageNumber(PageContext.TotalPagesSlot, style);
|
||||
}
|
||||
|
||||
public void PageNumberOfLocation(string locationName, TextStyle? style = null)
|
||||
{
|
||||
PageNumber(locationName, style);
|
||||
}
|
||||
|
||||
internal void Compose(IContainer container)
|
||||
{
|
||||
container.Stack(stack =>
|
||||
{
|
||||
stack.Spacing(Spacing);
|
||||
|
||||
foreach (var textBlock in TextBlocks)
|
||||
stack.Item().Element(textBlock);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -49,10 +119,9 @@ namespace QuestPDF.Fluent
|
||||
if (element is Alignment alignment)
|
||||
textBlock.Alignment = alignment.Horizontal;
|
||||
|
||||
var descriptor = new TextDescriptor(textBlock);
|
||||
var descriptor = new TextDescriptor();
|
||||
content?.Invoke(descriptor);
|
||||
|
||||
element.Element(textBlock);
|
||||
descriptor.Compose(element);
|
||||
}
|
||||
|
||||
public static void Text(this IContainer element, object text, TextStyle? style = null)
|
||||
|
||||
Reference in New Issue
Block a user