From 3ffab6e93329003cf39a11a116c98a9bc42933e3 Mon Sep 17 00:00:00 2001 From: Bebo-Maker Date: Tue, 12 Apr 2022 18:48:28 +0200 Subject: [PATCH 1/3] Implement superscript and subscript. --- QuestPDF/Drawing/FontManager.cs | 15 ++++++++++- QuestPDF/Elements/Text/Items/TextBlockSpan.cs | 18 ++++++++++--- .../Fluent/TextSpanDescriptorExtensions.cs | 25 ++++++++++++++++- QuestPDF/Fluent/TextStyleExtensions.cs | 27 ++++++++++++++++++- QuestPDF/Infrastructure/FontPosition.cs | 9 +++++++ QuestPDF/Infrastructure/TextStyle.cs | 6 ++++- 6 files changed, 93 insertions(+), 7 deletions(-) create mode 100644 QuestPDF/Infrastructure/FontPosition.cs diff --git a/QuestPDF/Drawing/FontManager.cs b/QuestPDF/Drawing/FontManager.cs index 98688a4..213a005 100644 --- a/QuestPDF/Drawing/FontManager.cs +++ b/QuestPDF/Drawing/FontManager.cs @@ -68,7 +68,7 @@ namespace QuestPDF.Drawing { Color = SKColor.Parse(style.Color), Typeface = GetTypeface(style), - TextSize = style.Size ?? 12, + TextSize = (style.Size ?? 12) * GetTextScale(style), IsAntialias = true, }; } @@ -76,6 +76,11 @@ namespace QuestPDF.Drawing static SKTypeface GetTypeface(TextStyle style) { var weight = (SKFontStyleWeight)(style.FontWeight ?? FontWeight.Normal); + + //Extra weight for superscript and subscript + if (style.FontPosition == FontPosition.Superscript || style.FontPosition == FontPosition.Subscript) + weight = (SKFontStyleWeight)((int)weight + 100); + var slant = (style.IsItalic ?? false) ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright; var fontStyle = new SKFontStyle(weight, SKFontStyleWidth.Normal, slant); @@ -100,5 +105,13 @@ namespace QuestPDF.Drawing { return FontMetrics.GetOrAdd(style.FontMetricsKey, key => style.ToPaint().FontMetrics); } + + private static float GetTextScale(TextStyle style) + { + if (style.FontPosition == FontPosition.Superscript || style.FontPosition == FontPosition.Subscript) + return 0.65f; + + return 1; + } } } \ No newline at end of file diff --git a/QuestPDF/Elements/Text/Items/TextBlockSpan.cs b/QuestPDF/Elements/Text/Items/TextBlockSpan.cs index 39d493e..7a5e500 100644 --- a/QuestPDF/Elements/Text/Items/TextBlockSpan.cs +++ b/QuestPDF/Elements/Text/Items/TextBlockSpan.cs @@ -126,23 +126,35 @@ namespace QuestPDF.Elements.Text.Items { var fontMetrics = Style.ToFontMetrics(); + var glyphOffsetY = GetVerticalGlyphOffsetForStyle(Style); + 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); + request.Canvas.DrawText(text, new Position(0, glyphOffsetY), Style); // draw underline if ((Style.HasUnderline ?? false) && fontMetrics.UnderlinePosition.HasValue) - DrawLine(fontMetrics.UnderlinePosition.Value, fontMetrics.UnderlineThickness ?? 1); + DrawLine(glyphOffsetY + fontMetrics.UnderlinePosition.Value, fontMetrics.UnderlineThickness ?? 1); // draw stroke if ((Style.HasStrikethrough ?? false) && fontMetrics.StrikeoutPosition.HasValue) - DrawLine(fontMetrics.StrikeoutPosition.Value, fontMetrics.StrikeoutThickness ?? 1); + DrawLine(glyphOffsetY + fontMetrics.StrikeoutPosition.Value, fontMetrics.StrikeoutThickness ?? 1); void DrawLine(float offset, float thickness) { request.Canvas.DrawRectangle(new Position(0, offset - thickness / 2f), new Size(request.TextSize.Width, thickness), Style.Color); } } + + private static float GetVerticalGlyphOffsetForStyle(TextStyle style) + { + if (style.FontPosition == FontPosition.Superscript) + return (style.Size ?? 12f) * -0.35f; + if (style.FontPosition == FontPosition.Subscript) + return (style.Size ?? 12f) * 0.1f; + + return 0; + } } } \ No newline at end of file diff --git a/QuestPDF/Fluent/TextSpanDescriptorExtensions.cs b/QuestPDF/Fluent/TextSpanDescriptorExtensions.cs index f30653b..41af136 100644 --- a/QuestPDF/Fluent/TextSpanDescriptorExtensions.cs +++ b/QuestPDF/Fluent/TextSpanDescriptorExtensions.cs @@ -126,7 +126,30 @@ namespace QuestPDF.Fluent { return descriptor.Weight(FontWeight.ExtraBlack); } - + + #endregion + + #region Position + public static T NormalPosition(this T descriptor) where T : TextSpanDescriptor + { + return descriptor.Position(FontPosition.Normal); + } + + public static T Subscript(this T descriptor) where T : TextSpanDescriptor + { + return descriptor.Position(FontPosition.Subscript); + } + + public static T Superscript(this T descriptor) where T : TextSpanDescriptor + { + return descriptor.Position(FontPosition.Superscript); + } + + private static T Position(this T descriptor, FontPosition fontPosition) where T : TextSpanDescriptor + { + descriptor.TextStyle.FontPosition = fontPosition; + return descriptor; + } #endregion } } \ No newline at end of file diff --git a/QuestPDF/Fluent/TextStyleExtensions.cs b/QuestPDF/Fluent/TextStyleExtensions.cs index 2686432..7823908 100644 --- a/QuestPDF/Fluent/TextStyleExtensions.cs +++ b/QuestPDF/Fluent/TextStyleExtensions.cs @@ -133,7 +133,32 @@ namespace QuestPDF.Fluent { return style.Weight(FontWeight.ExtraBlack); } - + + #endregion + + #region Position + public static TextStyle NormalPosition(this TextStyle style) + { + return style.Position(FontPosition.Normal); + } + + public static TextStyle Subscript(this TextStyle style) + { + return style.Position(FontPosition.Subscript); + } + + public static TextStyle Superscript(this TextStyle style) + { + return style.Position(FontPosition.Superscript); + } + + private static TextStyle Position(this TextStyle style, FontPosition fontPosition) + { + if (style.FontPosition == fontPosition) + return style; + + return style.Mutate(t => t.FontPosition = fontPosition); + } #endregion } } \ No newline at end of file diff --git a/QuestPDF/Infrastructure/FontPosition.cs b/QuestPDF/Infrastructure/FontPosition.cs new file mode 100644 index 0000000..d332740 --- /dev/null +++ b/QuestPDF/Infrastructure/FontPosition.cs @@ -0,0 +1,9 @@ +namespace QuestPDF.Infrastructure +{ + internal enum FontPosition + { + Normal = 0, + Subscript, + Superscript, + } +} diff --git a/QuestPDF/Infrastructure/TextStyle.cs b/QuestPDF/Infrastructure/TextStyle.cs index a0a72df..d734988 100644 --- a/QuestPDF/Infrastructure/TextStyle.cs +++ b/QuestPDF/Infrastructure/TextStyle.cs @@ -13,6 +13,7 @@ namespace QuestPDF.Infrastructure internal float? Size { get; set; } internal float? LineHeight { get; set; } internal FontWeight? FontWeight { get; set; } + internal FontPosition? FontPosition { get; set; } internal bool? IsItalic { get; set; } internal bool? HasStrikethrough { get; set; } internal bool? HasUnderline { get; set; } @@ -29,6 +30,7 @@ namespace QuestPDF.Infrastructure Size = 12, LineHeight = 1.2f, FontWeight = Infrastructure.FontWeight.Normal, + FontPosition = Infrastructure.FontPosition.Normal, IsItalic = false, HasStrikethrough = false, HasUnderline = false, @@ -45,7 +47,7 @@ namespace QuestPDF.Infrastructure HasGlobalStyleApplied = true; ApplyParentStyle(globalStyle); - PaintKey ??= (FontFamily, Size, FontWeight, IsItalic, Color); + PaintKey ??= (FontFamily, Size, FontWeight, FontPosition, IsItalic, Color); FontMetricsKey ??= (FontFamily, Size, FontWeight, IsItalic); } @@ -57,6 +59,7 @@ namespace QuestPDF.Infrastructure Size ??= parentStyle.Size; LineHeight ??= parentStyle.LineHeight; FontWeight ??= parentStyle.FontWeight; + FontPosition ??= parentStyle.FontPosition; IsItalic ??= parentStyle.IsItalic; HasStrikethrough ??= parentStyle.HasStrikethrough; HasUnderline ??= parentStyle.HasUnderline; @@ -71,6 +74,7 @@ namespace QuestPDF.Infrastructure Size = parentStyle.Size ?? Size; LineHeight = parentStyle.LineHeight ?? LineHeight; FontWeight = parentStyle.FontWeight ?? FontWeight; + FontPosition = parentStyle.FontPosition ?? FontPosition; IsItalic = parentStyle.IsItalic ?? IsItalic; HasStrikethrough = parentStyle.HasStrikethrough ?? HasStrikethrough; HasUnderline = parentStyle.HasUnderline ?? HasUnderline; From 82cd1d783511cfb928d17beccc440b92767a3efc Mon Sep 17 00:00:00 2001 From: Bebo-Maker Date: Tue, 12 Apr 2022 18:49:34 +0200 Subject: [PATCH 2/3] Add example for superscript/subscript. --- QuestPDF.Examples/TextExamples.cs | 46 ++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/QuestPDF.Examples/TextExamples.cs b/QuestPDF.Examples/TextExamples.cs index 1b26a24..0987ba9 100644 --- a/QuestPDF.Examples/TextExamples.cs +++ b/QuestPDF.Examples/TextExamples.cs @@ -36,7 +36,51 @@ namespace QuestPDF.Examples }); }); } - + + [Test] + public void SuperscriptSubscript() + { + RenderingTest + .Create() + .PageSize(500, 300) + + .ProduceImages() + .ShowResults() + .Render(container => + { + container + .Padding(5) + .MinimalBox() + .Border(1) + .Padding(10) + .Text(text => + { + text.Span("E=mc"); + text.Span("2").Superscript(); + + text.EmptyLine(); + + text.Span("H"); + text.Span("2").Subscript(); + text.Span("O"); + + text.EmptyLine(); + + text.ParagraphSpacing(2); + + var style = TextStyle.Default.Underline(); + + text.Span("Subscript").Style(style).Subscript().BackgroundColor(Colors.Green.Medium); + text.Span("Normal").Style(style).NormalPosition().BackgroundColor(Colors.Blue.Medium); + text.Line("Superscript").Style(style).Superscript().BackgroundColor(Colors.Red.Medium); + + text.Line("Superscript").Style(style).Superscript().BackgroundColor(Colors.Green.Medium); + text.Line("Normal").Style(style).NormalPosition().BackgroundColor(Colors.Blue.Medium); + text.Line("Subscript").Style(style).Subscript().BackgroundColor(Colors.Red.Medium); + }); + }); + } + [Test] public void ParagraphSpacing() { From c63b0658f352c56732659b3ec2390ade90596481 Mon Sep 17 00:00:00 2001 From: Bebo-Maker Date: Tue, 12 Apr 2022 19:14:00 +0200 Subject: [PATCH 3/3] Ignore FontPosition when calculating font metrics. --- QuestPDF/Drawing/FontManager.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/QuestPDF/Drawing/FontManager.cs b/QuestPDF/Drawing/FontManager.cs index 213a005..281028f 100644 --- a/QuestPDF/Drawing/FontManager.cs +++ b/QuestPDF/Drawing/FontManager.cs @@ -2,6 +2,7 @@ using System.Collections.Concurrent; using System.IO; using System.Linq; +using QuestPDF.Fluent; using QuestPDF.Infrastructure; using SkiaSharp; @@ -103,7 +104,7 @@ namespace QuestPDF.Drawing internal static SKFontMetrics ToFontMetrics(this TextStyle style) { - return FontMetrics.GetOrAdd(style.FontMetricsKey, key => style.ToPaint().FontMetrics); + return FontMetrics.GetOrAdd(style.FontMetricsKey, key => style.NormalPosition().ToPaint().FontMetrics); } private static float GetTextScale(TextStyle style)