From f4181de3d22868faeb0e7b2ea3c35d55dea88348 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Zi=C4=85bek?= Date: Wed, 28 Apr 2021 00:24:26 +0200 Subject: [PATCH] Simple drawing text --- QuestPDF.Examples/TextExample.cs | 147 +++++++++++++++++++++++++ QuestPDF/Drawing/CanvasCache.cs | 2 +- QuestPDF/Elements/Stack.cs | 10 +- QuestPDF/Elements/TextElement.cs | 55 +++++++++ QuestPDF/Elements/TextRun.cs | 48 ++++++++ QuestPDF/Fluent/TextStyleExtensions.cs | 5 + QuestPDF/Helpers/Placeholders.cs | 2 +- QuestPDF/Infrastructure/TextStyle.cs | 8 +- 8 files changed, 269 insertions(+), 8 deletions(-) create mode 100644 QuestPDF.Examples/TextExample.cs create mode 100644 QuestPDF/Elements/TextElement.cs create mode 100644 QuestPDF/Elements/TextRun.cs diff --git a/QuestPDF.Examples/TextExample.cs b/QuestPDF.Examples/TextExample.cs new file mode 100644 index 0000000..7c9dd0b --- /dev/null +++ b/QuestPDF.Examples/TextExample.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using QuestPDF.Elements; +using QuestPDF.Examples.Engine; +using QuestPDF.Fluent; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Examples +{ + public class TextExample : ExampleTestBase + { + [ShowResult] + [ImageSize(1200, 500)] + public void Test(IContainer container) + { + List Lorem() + { + return new List + { + new TextElement() + { + Style = TextStyle.Default.Size(32).BackgroundColor(Colors.Red.Lighten3), + Text = "Lorem ipsum " + }, + new TextElement() + { + Style = TextStyle.Default.Size(24).BackgroundColor(Colors.Orange.Lighten3), + Text = " dolor " + }, + new TextElement() + { + Style = TextStyle.Default.Size(64).BackgroundColor(Colors.Yellow.Lighten3), + Text = " sit " + }, + new TextElement() + { + Style = TextStyle.Default.Size(32).BackgroundColor(Colors.Green.Lighten3), + Text = " amet" + } + }; + } + + Func> Source = Lorem ; + + container + .Padding(50) + .Stack(row => + { + row.Spacing(50); + + row.Element().Box().Border(1).Stack(stack => + { + stack + .Element() + .Box() + //.Background(Placeholders.BackgroundColor()) + .Element(new TextRun() + { + Elements = Source() + }); + + stack + .Element() + .Box() + //.Background(Placeholders.BackgroundColor()) + .Element(new TextRun() + { + Elements = Source() + }); + + stack + .Element() + .Box() + //.Background(Placeholders.BackgroundColor()) + .Element(new TextRun() + { + Elements = Split(Source()) + }); + }); + + row.Element().Box().Border(1).Stack(stack => + { + stack + .Element() + .Box() + .Background(Placeholders.BackgroundColor()) + .Element(new TextRun() + { + Elements = Dense(Source()) + }); + + stack + .Element() + .Box() + .Background(Placeholders.BackgroundColor()) + .Element(new TextRun() + { + Elements = Dense(Source()) + }); + + stack + .Element() + .Box() + .Background(Placeholders.BackgroundColor()) + .Element(new TextRun() + { + Elements = Dense(Split(Source())) + }); + }); + }); + } + + List RandomText() + { + return Placeholders + .Sentence() + .Split(" ") + .Select(x => new TextElement + { + Text = $"{x} ", + Style = TextStyle.Default.Size(Placeholders.Random.Next(24, 64)) + }) + .ToList(); + } + + List Split(List elements) + { + return elements + .SelectMany(x => x + .Text + .Select(y => new TextElement + { + Style = x.Style, + Text = y.ToString() + })) + .ToList(); + } + + List Dense(List elements) + { + elements.ForEach(x => x.Style.IsDense = true); + return elements; + } + } +} \ No newline at end of file diff --git a/QuestPDF/Drawing/CanvasCache.cs b/QuestPDF/Drawing/CanvasCache.cs index 9037309..b7f321d 100644 --- a/QuestPDF/Drawing/CanvasCache.cs +++ b/QuestPDF/Drawing/CanvasCache.cs @@ -37,7 +37,7 @@ namespace QuestPDF.Drawing Typeface = SKTypeface.FromFamilyName(style.FontType, (int)style.FontWeight, (int)SKFontStyleWidth.Normal, slant), TextSize = style.Size, TextEncoding = SKTextEncoding.Utf32, - + TextAlign = style.Alignment switch { HorizontalAlignment.Left => SKTextAlign.Left, diff --git a/QuestPDF/Elements/Stack.cs b/QuestPDF/Elements/Stack.cs index dbb2e39..09a3166 100644 --- a/QuestPDF/Elements/Stack.cs +++ b/QuestPDF/Elements/Stack.cs @@ -24,6 +24,7 @@ namespace QuestPDF.Elements return new FullRender(Size.Zero); var heightOnCurrentPage = 0f; + var maxWidth = 0f; foreach (var renderer in ChildrenQueue) { @@ -34,17 +35,20 @@ namespace QuestPDF.Elements if (heightOnCurrentPage < Size.Epsilon) return new Wrap(); - return new PartialRender(availableSpace.Width, heightOnCurrentPage); + return new PartialRender(maxWidth, heightOnCurrentPage); } var size = space as Size; heightOnCurrentPage += size.Height; + if (size.Width > maxWidth) + maxWidth = size.Width; + if (space is PartialRender) - return new PartialRender(availableSpace.Width, heightOnCurrentPage); + return new PartialRender(maxWidth, heightOnCurrentPage); } - return new FullRender(availableSpace.Width, heightOnCurrentPage); + return new FullRender(maxWidth, heightOnCurrentPage); } internal override void Draw(ICanvas canvas, Size availableSpace) diff --git a/QuestPDF/Elements/TextElement.cs b/QuestPDF/Elements/TextElement.cs new file mode 100644 index 0000000..7294d34 --- /dev/null +++ b/QuestPDF/Elements/TextElement.cs @@ -0,0 +1,55 @@ +using System.Collections.Concurrent; +using System.Collections.Generic; +using QuestPDF.Drawing; +using QuestPDF.Drawing.SpacePlan; +using QuestPDF.Infrastructure; +using SkiaSharp; + +namespace QuestPDF.Elements +{ + public struct TextMeasurement + { + public float Width { get; set; } + public float Height { get; set; } + public SKRect Position { get; set; } + } + + internal class TextElement + { + public TextStyle Style { get; set; } + public string Text { get; set; } + + + private static ConcurrentDictionary Measurements = new ConcurrentDictionary(); + + public TextMeasurement Measure() + { + return Measure(Text, Style); + } + + public void Draw(ICanvas canvas) + { + (canvas as QuestPDF.Drawing.Canvas).SkiaCanvas.DrawText(Text, 0, 0, Style.ToPaint()); + } + + internal static TextMeasurement Measure(string text, TextStyle style) + { + var key = $"{text}_{style}"; + + return Measurements.GetOrAdd(key, x => + { + var rect = new SKRect(); + var width = style.ToPaint().MeasureText(text, ref rect); + + return new TextMeasurement + { + Position = rect, + Height = style.IsDense + ? rect.Bottom + rect.Height + : style.LineHeight * style.Size, + Width = width + }; + }); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Elements/TextRun.cs b/QuestPDF/Elements/TextRun.cs new file mode 100644 index 0000000..e3ee5ff --- /dev/null +++ b/QuestPDF/Elements/TextRun.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.Linq; +using QuestPDF.Drawing.SpacePlan; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Elements +{ + internal class TextRun : Element + { + public List Elements = new List(); + + internal override ISpacePlan Measure(Size availableSpace) + { + var measurements = Elements.Select(x => x.Measure()).ToList(); + + var width = measurements.Sum(x => x.Width); + //var height = measurements.Max(x => x.Height); + var height = measurements.Max(x => x.Position.Bottom) + measurements.Max(x => x.Position.Height); + + + return new FullRender(width, height); + } + + internal override void Draw(ICanvas canvas, Size availableSpace) + { + var measurements = Elements.Select(x => x.Measure()).ToList(); + + var top = measurements.Max(x => x.Position.Height); + var bottom = measurements.Max(x => x.Position.Bottom); + + var offset = measurements.Min(x => x.Position.Top); + canvas.Translate(new Position(0, -offset)); + + foreach (var textElement in Elements) + { + var size = textElement.Measure(); + + canvas.DrawRectangle(new Position(0, -top), new Size(size.Width, bottom + top), textElement.Style.BackgroundColor); + //canvas.DrawRectangle(new Position(size.Position.Left, size.Position.Top), new Size(size.Position.Width, size.Position.Height), textElement.Style.BackgroundColor); + textElement.Draw(canvas); + canvas.Translate(new Position(size.Width, 0)); + } + + canvas.Translate(new Position(-measurements.Sum(x => x.Width), offset)); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Fluent/TextStyleExtensions.cs b/QuestPDF/Fluent/TextStyleExtensions.cs index 4a74abc..39577b7 100644 --- a/QuestPDF/Fluent/TextStyleExtensions.cs +++ b/QuestPDF/Fluent/TextStyleExtensions.cs @@ -18,6 +18,11 @@ namespace QuestPDF.Fluent return style.Mutate(x => x.Color = value); } + public static TextStyle BackgroundColor(this TextStyle style, string value) + { + return style.Mutate(x => x.BackgroundColor = value); + } + public static TextStyle FontType(this TextStyle style, string value) { return style.Mutate(x => x.FontType = value); diff --git a/QuestPDF/Helpers/Placeholders.cs b/QuestPDF/Helpers/Placeholders.cs index 8289799..d7bfc64 100644 --- a/QuestPDF/Helpers/Placeholders.cs +++ b/QuestPDF/Helpers/Placeholders.cs @@ -7,7 +7,7 @@ namespace QuestPDF.Helpers { public static class Placeholders { - private static Random Random = new Random(); + public static Random Random = new Random(); #region Word Cache diff --git a/QuestPDF/Infrastructure/TextStyle.cs b/QuestPDF/Infrastructure/TextStyle.cs index d12e82b..0eadab3 100644 --- a/QuestPDF/Infrastructure/TextStyle.cs +++ b/QuestPDF/Infrastructure/TextStyle.cs @@ -5,18 +5,20 @@ namespace QuestPDF.Infrastructure public class TextStyle { internal string Color { get; set; } = Colors.Black; + internal string BackgroundColor { get; set; } = Colors.Transparent; internal string FontType { get; set; } = "Calibri"; internal float Size { get; set; } = 12; - internal float LineHeight { get; set; } = 1.2f; + internal float LineHeight { get; set; } = 1f; internal HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left; internal FontWeight FontWeight { get; set; } = FontWeight.Normal; internal bool IsItalic { get; set; } = false; - + internal bool IsDense { get; set; } + public static TextStyle Default => new TextStyle(); public override string ToString() { - return $"{Color}|{FontType}|{Size}|{LineHeight}|{Alignment}|{FontWeight}|{IsItalic}"; + return $"{Color}|{BackgroundColor}|{FontType}|{Size}|{LineHeight}|{Alignment}|{FontWeight}|{IsItalic}|{IsDense}"; } internal TextStyle Clone() => (TextStyle)MemberwiseClone();