From bbf3d71e7edbef5a4a00d12e5ea421004e15db37 Mon Sep 17 00:00:00 2001 From: Bennet Bo Fenner <53836821+Bebo-Maker@users.noreply.github.com> Date: Thu, 3 Nov 2022 16:43:29 +0100 Subject: [PATCH 1/6] add letter spacing to TextStyle --- QuestPDF/Fluent/TextSpanDescriptorExtensions.cs | 8 +++++++- QuestPDF/Fluent/TextStyleExtensions.cs | 7 ++++++- QuestPDF/Infrastructure/TextStyle.cs | 2 ++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/QuestPDF/Fluent/TextSpanDescriptorExtensions.cs b/QuestPDF/Fluent/TextSpanDescriptorExtensions.cs index 23cda24..522e90d 100644 --- a/QuestPDF/Fluent/TextSpanDescriptorExtensions.cs +++ b/QuestPDF/Fluent/TextSpanDescriptorExtensions.cs @@ -55,7 +55,13 @@ namespace QuestPDF.Fluent descriptor.MutateTextStyle(x => x.LineHeight(value)); return descriptor; } - + + public static T LetterSpacing(this T descriptor, float value) where T : TextSpanDescriptor + { + descriptor.MutateTextStyle(x => x.LetterSpacing(value)); + return descriptor; + } + public static T Italic(this T descriptor, bool value = true) where T : TextSpanDescriptor { descriptor.MutateTextStyle(x => x.Italic(value)); diff --git a/QuestPDF/Fluent/TextStyleExtensions.cs b/QuestPDF/Fluent/TextStyleExtensions.cs index 6119f6d..77b967c 100644 --- a/QuestPDF/Fluent/TextStyleExtensions.cs +++ b/QuestPDF/Fluent/TextStyleExtensions.cs @@ -48,7 +48,12 @@ namespace QuestPDF.Fluent { return style.Mutate(TextStyleProperty.LineHeight, value); } - + + public static TextStyle LetterSpacing(this TextStyle style, float value) + { + return style.Mutate(TextStyleProperty.LetterSpacing, value); + } + public static TextStyle Italic(this TextStyle style, bool value = true) { return style.Mutate(TextStyleProperty.IsItalic, value); diff --git a/QuestPDF/Infrastructure/TextStyle.cs b/QuestPDF/Infrastructure/TextStyle.cs index 6f498ad..8385876 100644 --- a/QuestPDF/Infrastructure/TextStyle.cs +++ b/QuestPDF/Infrastructure/TextStyle.cs @@ -10,6 +10,7 @@ namespace QuestPDF.Infrastructure internal string? FontFamily { get; set; } internal float? Size { get; set; } internal float? LineHeight { get; set; } + internal float? LetterSpacing { get; set; } internal FontWeight? FontWeight { get; set; } internal FontPosition? FontPosition { get; set; } internal bool? IsItalic { get; set; } @@ -26,6 +27,7 @@ namespace QuestPDF.Infrastructure FontFamily = Fonts.Lato, Size = 12, LineHeight = 1.2f, + LetterSpacing = 0, FontWeight = Infrastructure.FontWeight.Normal, FontPosition = Infrastructure.FontPosition.Normal, IsItalic = false, From e6bd63b3ec841cef77ec6bfeecfc784fe4c8ff8a Mon Sep 17 00:00:00 2001 From: Bennet Bo Fenner <53836821+Bebo-Maker@users.noreply.github.com> Date: Thu, 3 Nov 2022 16:43:49 +0100 Subject: [PATCH 2/6] handle letter spacing in TextStyleManager --- QuestPDF/Infrastructure/TextStyleManager.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/QuestPDF/Infrastructure/TextStyleManager.cs b/QuestPDF/Infrastructure/TextStyleManager.cs index 031c56b..373cea3 100644 --- a/QuestPDF/Infrastructure/TextStyleManager.cs +++ b/QuestPDF/Infrastructure/TextStyleManager.cs @@ -11,6 +11,7 @@ namespace QuestPDF.Infrastructure FontFamily, Size, LineHeight, + LetterSpacing, FontWeight, FontPosition, IsItalic, @@ -101,6 +102,19 @@ namespace QuestPDF.Infrastructure return origin with { LineHeight = castedValue }; } + + if(property == TextStyleProperty.LetterSpacing) + { + if (!overrideValue && origin.LetterSpacing != null) + return origin; + + var castedValue = (float?)value; + + if (origin.LetterSpacing == castedValue) + return origin; + + return origin with { LetterSpacing = castedValue }; + } if (property == TextStyleProperty.FontWeight) { @@ -232,6 +246,7 @@ namespace QuestPDF.Infrastructure result = MutateStyle(result, TextStyleProperty.FontFamily, parent.FontFamily, overrideStyle); result = MutateStyle(result, TextStyleProperty.Size, parent.Size, overrideStyle); result = MutateStyle(result, TextStyleProperty.LineHeight, parent.LineHeight, overrideStyle); + result = MutateStyle(result, TextStyleProperty.LetterSpacing, parent.LetterSpacing, overrideStyle); result = MutateStyle(result, TextStyleProperty.FontWeight, parent.FontWeight, overrideStyle); result = MutateStyle(result, TextStyleProperty.FontPosition, parent.FontPosition, overrideStyle); result = MutateStyle(result, TextStyleProperty.IsItalic, parent.IsItalic, overrideStyle); From 823572bf251d047cf488ab8f981568da23129b66 Mon Sep 17 00:00:00 2001 From: Bennet Bo Fenner <53836821+Bebo-Maker@users.noreply.github.com> Date: Thu, 3 Nov 2022 16:45:40 +0100 Subject: [PATCH 3/6] implement letter spacing --- QuestPDF/Drawing/TextShaper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QuestPDF/Drawing/TextShaper.cs b/QuestPDF/Drawing/TextShaper.cs index 509482c..5772fbf 100644 --- a/QuestPDF/Drawing/TextShaper.cs +++ b/QuestPDF/Drawing/TextShaper.cs @@ -52,7 +52,7 @@ namespace QuestPDF.Drawing Width = glyphPositions[i].XAdvance * scaleX }; - xOffset += glyphPositions[i].XAdvance * scaleX; + xOffset += (glyphPositions[i].XAdvance * scaleX) + TextStyle.LetterSpacing ?? 0; yOffset += glyphPositions[i].YAdvance * scaleY; } From 4345ce857f82e3d128cfe06353f26074f7e2781c Mon Sep 17 00:00:00 2001 From: Bennet Bo Fenner <53836821+Bebo-Maker@users.noreply.github.com> Date: Thu, 3 Nov 2022 16:45:49 +0100 Subject: [PATCH 4/6] add example for letter spacing --- QuestPDF.Examples/TextExamples.cs | 44 ++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/QuestPDF.Examples/TextExamples.cs b/QuestPDF.Examples/TextExamples.cs index a3c0c12..ed818f5 100644 --- a/QuestPDF.Examples/TextExamples.cs +++ b/QuestPDF.Examples/TextExamples.cs @@ -122,7 +122,49 @@ namespace QuestPDF.Examples }); }); } - + + [Test] + public void LetterSpacing() + { + RenderingTest + .Create() + .PageSize(500, 700) + .ProduceImages() + .ShowResults() + .Render(container => + { + container + .Padding(20) + .Column(column => + { + var letterSpacing = new[] { -1f, 0f, 2f }; + var paragraph = Placeholders.Sentence(); + + foreach (var spacing in letterSpacing) + { + column + .Item() + .Border(1) + .Padding(10) + .Column(nestedColumn => + { + nestedColumn.Item() + .Text(paragraph) + .FontSize(16) + .LetterSpacing(spacing); + + nestedColumn.Item() + .Text($"Letter spacing of {spacing} pt") + .FontSize(10) + .Italic() + .FontColor(Colors.Blue.Medium); + }); + + } + }); + }); + } + [Test] public void SuperscriptSubscript_Simple() { From e4f8243c9be6e563d9596db910c7ffeff2dbd462 Mon Sep 17 00:00:00 2001 From: Bennet Bo Fenner <53836821+Bebo-Maker@users.noreply.github.com> Date: Fri, 18 Nov 2022 15:32:04 +0100 Subject: [PATCH 5/6] add more examples for letter spacing --- QuestPDF.Examples/TextExamples.cs | 78 +++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/QuestPDF.Examples/TextExamples.cs b/QuestPDF.Examples/TextExamples.cs index ed818f5..1f72938 100644 --- a/QuestPDF.Examples/TextExamples.cs +++ b/QuestPDF.Examples/TextExamples.cs @@ -165,6 +165,84 @@ namespace QuestPDF.Examples }); } + [Test] + public void LetterSpacing_Arabic() + { + RenderingTest + .Create() + .PageSize(500, 700) + .ProduceImages() + .ShowResults() + .Render(container => + { + container + .Padding(50) + .Column(column => + { + var letterSpacing = new[] { -1f, 0f, 2f }; + var paragraph = "ينا الألم. في بعض الأحيان ونظراً للالتزامات التي يفرضها علينا"; + foreach (var spacing in letterSpacing) + { + column + .Item() + .Border(1) + .Padding(10) + .Column(nestedColumn => + { + nestedColumn.Item() + .Text(paragraph) + .FontSize(16) + .FontFamily(Fonts.Calibri) + .LetterSpacing(spacing); + + nestedColumn.Item() + .Text($"Letter spacing of {spacing} pt") + .FontSize(10) + .Italic() + .FontColor(Colors.Blue.Medium); + }); + } + }); + }); + } + + + [Test] + public void LetterSpacing_Unicode() + { + RenderingTest + .Create() + .PageSize(500, 700) + .ProduceImages() + .ShowResults() + .Render(container => + { + container + .Padding(50) + .Column(column => + { + var letterSpacing = new[] { 0f, 20f }; + var paragraph = "Ţ̴̡̧̤̮̺̤̗͎̱̹͙͎͖͂̿̓́̉̊̀̍͜h̵̞̘͇̾̎̏̅į̵̹̖͔͉̰̎̉̄̐̏͑͂̅̃̃͘͝s̷͓͉̭̭̯̬̥̻̰̩̦̑̀̀͌́̒̍̒̌̇͛̀͛́̎ ̷̡̡̟͕̳̺̝̼͇͔̬̟̖͍̈́̽͜͝͝i̶͔͚̟̊̐͛́͛̄̌ṡ̸̡̤̪͙͍̥͙̟̼̝̰̥͈̿̓̄̿̓͠ ̶̢̦̙͍̯̖̱̰̯͕͔͎̯̝̎͑t̸͖̲̱̼̎͐̎̉̾̎̾̌̅̔̏͘ȩ̶̝̫̙͓̙̣̔̀̌̔̋̂̑̈́̏̀̈͘̕͜͝s̸̫̝̮̻̼͐̅̄̎̎̑͝ț̷̨̢̨̻͈̮̞̆͗̓͊̃̌͂̑̉̕̕͜͝͝"; + + foreach (var spacing in letterSpacing) + { + column.Item() + .Text($"Letter spacing of {spacing} pt") + .FontSize(10) + .Italic() + .FontColor(Colors.Blue.Medium); + + column.Item() + .PaddingVertical(50) + .Text(paragraph) + .FontSize(16) + .FontFamily(Fonts.Calibri) + .LetterSpacing(spacing); + } + }); + }); + } + [Test] public void SuperscriptSubscript_Simple() { From 19ff9062ad5cfc9ad16c096af16acbb13a4abdc9 Mon Sep 17 00:00:00 2001 From: Bennet Bo Fenner <53836821+Bebo-Maker@users.noreply.github.com> Date: Fri, 18 Nov 2022 15:34:30 +0100 Subject: [PATCH 6/6] apply letter spacing to each unicode cluster --- QuestPDF/Drawing/TextShaper.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/QuestPDF/Drawing/TextShaper.cs b/QuestPDF/Drawing/TextShaper.cs index 5772fbf..556066a 100644 --- a/QuestPDF/Drawing/TextShaper.cs +++ b/QuestPDF/Drawing/TextShaper.cs @@ -41,18 +41,25 @@ namespace QuestPDF.Drawing var xOffset = 0f; var yOffset = 0f; + uint lastCluster = glyphInfos.LastOrDefault().Cluster; + var glyphs = new ShapedGlyph[length]; - for (var i = 0; i < length; i++) { + //We need to advance xOffset by the current letter spacing after each unicode cluster. + if (lastCluster != glyphInfos[i].Cluster) + { + lastCluster = glyphInfos[i].Cluster; + xOffset += TextStyle.LetterSpacing ?? 0; + } + glyphs[i] = new ShapedGlyph { Codepoint = (ushort)glyphInfos[i].Codepoint, Position = new SKPoint(xOffset + glyphPositions[i].XOffset * scaleX, yOffset - glyphPositions[i].YOffset * scaleY), Width = glyphPositions[i].XAdvance * scaleX - }; - - xOffset += (glyphPositions[i].XAdvance * scaleX) + TextStyle.LetterSpacing ?? 0; + }; + xOffset += glyphPositions[i].XAdvance * scaleX; yOffset += glyphPositions[i].YAdvance * scaleY; }