diff --git a/QuestPDF.Examples/TextExamples.cs b/QuestPDF.Examples/TextExamples.cs index 52f1aa6..0db0b11 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() { diff --git a/QuestPDF/Drawing/FontManager.cs b/QuestPDF/Drawing/FontManager.cs index 98688a4..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; @@ -68,7 +69,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 +77,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); @@ -98,7 +104,15 @@ 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) + { + 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 543c29c..147efcc 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;