Compare commits

...

6 Commits

Author SHA1 Message Date
Marcin Ziąbek e18b5170e6 Improved text run calculation 2021-05-05 11:21:53 +02:00
Marcin Ziąbek 5010b61565 Merge branch 'main' into richtext
# Conflicts:
#	QuestPDF/Elements/Stack.cs
2021-05-05 00:16:45 +02:00
Marcin Ziąbek f8d270bd2a Font measurement improvements 2021-05-04 15:33:10 +02:00
Marcin Ziąbek f884547d16 First steps with text shaper 2021-04-29 11:18:16 +02:00
Marcin Ziąbek 4a7f9385c4 Improved text positioning 2021-04-28 23:49:22 +02:00
Marcin Ziąbek f4181de3d2 Simple drawing text 2021-04-28 00:24:26 +02:00
9 changed files with 373 additions and 27 deletions
+212
View File
@@ -0,0 +1,212 @@
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;
using SkiaSharp;
namespace QuestPDF.Examples
{
public class TextExample : ExampleTestBase
{
[ShowResult]
[ImageSize(1400, 600)]
public void OldText(IContainer container)
{
var fonts = new[]
{
Fonts.Arial,
Fonts.Calibri,
Fonts.Cambria,
Fonts.Candara,
Fonts.ComicSans,
Fonts.Consolas,
Fonts.Corbel,
Fonts.Courier,
Fonts.CourierNew,
Fonts.Georgia,
Fonts.Impact,
Fonts.LucidaConsole,
Fonts.SegoeSD,
Fonts.SegoeUI,
Fonts.Tahoma,
Fonts.TimesNewRoman,
Fonts.TimesRoman,
Fonts.Trebuchet,
Fonts.Verdana,
};
container
.Padding(50)
.Grid(stack =>
{
stack.Spacing(10);
stack.Columns(2);
foreach (var font in fonts)
{
stack
.Element()
.Box()
.Background(Placeholders.BackgroundColor())
.Text($"Lorem ipsum dolor sit amet {font}", TextStyle.Default.Size(24).FontType(font));
}
});
}
//[ShowResult]
[ImageSize(1400, 800)]
public void Test(IContainer container)
{
List<TextElement> Lorem()
{
return new List<TextElement>
{
new TextElement()
{
Style = TextStyle.Default.Size(32).BackgroundColor(Placeholders.BackgroundColor()),
Text = "Podstawowy łaciński Tabela znaków Unicode"
},
new TextElement()
{
Style = TextStyle.Default.FontType("Segoe UI Emoji").Size(32).BackgroundColor(Placeholders.BackgroundColor()),
Text = "✔"
},
new TextElement()
{
Style = TextStyle.Default.FontType("Segoe UI Emoji").Size(32).BackgroundColor(Placeholders.BackgroundColor()),
Text = "🥛"
},
new TextElement()
{
Style = TextStyle.Default.FontType("Segoe UI Emoji").Size(32).BackgroundColor(Placeholders.BackgroundColor()),
Text = "🧀"
},
new TextElement()
{
Style = TextStyle.Default.FontType("Segoe UI Emoji").Size(32).BackgroundColor(Placeholders.BackgroundColor()),
Text = "❤🚵‍♀️"
},
new TextElement()
{
Style = TextStyle.Default.Size(32).BackgroundColor(Placeholders.BackgroundColor()),
Text = "Lorem ipsum "
},
new TextElement()
{
Style = TextStyle.Default.Size(24).BackgroundColor(Placeholders.BackgroundColor()),
Text = " dolor "
},
new TextElement()
{
Style = TextStyle.Default.Size(64).BackgroundColor(Placeholders.BackgroundColor()),
Text = " sijt "
},
new TextElement()
{
Style = TextStyle.Default.Size(32).BackgroundColor(Placeholders.BackgroundColor()),
Text = " amet"
}
};
}
Func<List<TextElement>> Source = () => Split(RandomText());
container
.Padding(50)
.Box().Border(1).Stack(stack =>
{
stack
.Element()
.Box()
//.Background(Placeholders.BackgroundColor())
.Element(new TextRun()
{
Elements = Lorem()
});
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 = Source()
});
stack
.Element()
.Box()
//.Background(Placeholders.BackgroundColor())
.Element(new TextRun()
{
Elements = Source()
});
stack
.Element()
.Box()
//.Background(Placeholders.BackgroundColor())
.Element(new TextRun()
{
Elements = Source()
});
});
}
List<TextElement> RandomText()
{
var sizes = new[] { 24, 32, 48, 64};
return Placeholders
.Sentence()
.Split(" ")
.Select(x => new TextElement
{
Text = $"{x} ",
Style = TextStyle
.Default
//.Size(sizes[Placeholders.Random.Next(0, 3)])
.Size(24)
.BackgroundColor(Placeholders.BackgroundColor())
//.LineHeight((float)Placeholders.Random.NextDouble() / 2 + 1f)
.LineHeight(1.2f)
})
.ToList();
}
List<TextElement> Split(List<TextElement> elements)
{
return elements
.SelectMany(x => x
.Text
.Select(y => new TextElement
{
Style = x.Style,
Text = y.ToString()
}))
.ToList();
}
}
}
+1 -1
View File
@@ -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,
+8 -3
View File
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using QuestPDF.Drawing;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
using Size = QuestPDF.Infrastructure.Size;
@@ -24,7 +25,8 @@ namespace QuestPDF.Elements
.DefaultIfEmpty(0)
.Max();
var realHeight = lines.Count * LineHeight;
var paint = Style.ToPaint().FontMetrics;
var realHeight = -paint.Ascent + paint.Descent;
if (realHeight > availableSpace.Height + Size.Epsilon)
return new Wrap();
@@ -34,12 +36,15 @@ namespace QuestPDF.Elements
internal override void Draw(ICanvas canvas, Size availableSpace)
{
var paint = Style.ToPaint().FontMetrics;
var offeset = -paint.Ascent; // paint.Ascent - Style.Size;
var lines = BreakLines(availableSpace.Width);
var offsetTop = 0f;
var offsetLeft = GetLeftOffset();
canvas.Translate(new Position(0, Style.Size));
canvas.Translate(new Position(0, offeset));
foreach (var line in lines)
{
@@ -47,7 +52,7 @@ namespace QuestPDF.Elements
offsetTop += LineHeight;
}
canvas.Translate(new Position(0, -Style.Size));
canvas.Translate(new Position(0, -offeset));
float GetLeftOffset()
{
+51
View File
@@ -0,0 +1,51 @@
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 SKRect Position { get; set; }
}
internal class TextElement
{
public TextStyle Style { get; set; }
public string Text { get; set; }
private static ConcurrentDictionary<string, TextMeasurement> Measurements = new ConcurrentDictionary<string, TextMeasurement>();
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,
Width = width
};
});
}
}
}
+71
View File
@@ -0,0 +1,71 @@
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Drawing;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace QuestPDF.Elements
{
internal class TextRun : Element
{
public List<TextElement> Elements = new List<TextElement>();
internal override ISpacePlan Measure(Size availableSpace)
{
var measurements = Elements.Select(x => x.Measure()).ToList();
var metrics = Elements
.Select(x => x.Style.ToPaint())
.Select(x => x.FontMetrics)
.ToList();
var lineHeight = metrics.Max(x => -x.Ascent) + metrics.Max(x => x.Descent);
var width = measurements.Sum(x => x.Width);
return new FullRender(width, lineHeight);
}
internal override void Draw(ICanvas canvas, Size availableSpace)
{
var measurements = Elements
.Select(x => x.Measure())
.ToList();
var metrics = Elements
.Select(x => x.Style.ToPaint())
.Select(x => x.FontMetrics)
.ToList();
var offset = metrics.Max(x => -x.Ascent);
var lineHeight = metrics.Max(x => -x.Ascent) + metrics.Max(x => x.Descent);
using var typeface = SKTypeface.FromFamilyName("Helvetica");
using var shaper = new SKShaper(typeface);
var shaped = shaper.Shape("Podstawowy łaciński — ✔️ ❤️ ☆ Tabela znaków Unicode", Elements.First().Style.ToPaint());
foreach (var textElement in Elements)
{
var size = textElement.Measure();
canvas.DrawRectangle(new Position(0, 0), new Size(size.Width, lineHeight), textElement.Style.BackgroundColor);
canvas.Translate(new Position(0, offset));
textElement.Draw(canvas);
canvas.Translate(new Position(0, -offset));
canvas.Translate(new Position(size.Width, 0));
}
canvas.Translate(new Position(-measurements.Sum(x => x.Width), 0));
//
}
}
}
+5
View File
@@ -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);
+20 -20
View File
@@ -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
@@ -155,25 +155,25 @@ namespace QuestPDF.Helpers
private static readonly string[] BackgroundColors =
{
Colors.Red.Lighten2,
Colors.Pink.Lighten2,
Colors.Purple.Lighten2,
Colors.DeepPurple.Lighten2,
Colors.Indigo.Lighten2,
Colors.Blue.Lighten2,
Colors.LightBlue.Lighten2,
Colors.Cyan.Lighten2,
Colors.Teal.Lighten2,
Colors.Green.Lighten2,
Colors.LightGreen.Lighten2,
Colors.Lime.Lighten2,
Colors.Yellow.Lighten2,
Colors.Amber.Lighten2,
Colors.Orange.Lighten2,
Colors.DeepOrange.Lighten2,
Colors.Brown.Lighten2,
Colors.Grey.Lighten2,
Colors.BlueGrey.Lighten2
Colors.Red.Lighten3,
Colors.Pink.Lighten3,
Colors.Purple.Lighten3,
Colors.DeepPurple.Lighten3,
Colors.Indigo.Lighten3,
Colors.Blue.Lighten3,
Colors.LightBlue.Lighten3,
Colors.Cyan.Lighten3,
Colors.Teal.Lighten3,
Colors.Green.Lighten3,
Colors.LightGreen.Lighten3,
Colors.Lime.Lighten3,
Colors.Yellow.Lighten3,
Colors.Amber.Lighten3,
Colors.Orange.Lighten3,
Colors.DeepOrange.Lighten3,
Colors.Brown.Lighten3,
Colors.Grey.Lighten3,
Colors.BlueGrey.Lighten3
};
public static string BackgroundColor()
+4 -3
View File
@@ -5,18 +5,19 @@ 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;
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}";
}
internal TextStyle Clone() => (TextStyle)MemberwiseClone();
+1
View File
@@ -23,6 +23,7 @@
<ItemGroup>
<PackageReference Include="SkiaSharp" Version="2.80.2" />
<PackageReference Include="SkiaSharp.HarfBuzz" Version="2.80.2" />
</ItemGroup>
<ItemGroup>