diff --git a/QuestPDF.Examples/TextExamples.cs b/QuestPDF.Examples/TextExamples.cs index 0db0b11..d0d05ba 100644 --- a/QuestPDF.Examples/TextExamples.cs +++ b/QuestPDF.Examples/TextExamples.cs @@ -38,12 +38,11 @@ namespace QuestPDF.Examples } [Test] - public void SuperscriptSubscript() + public void SuperscriptSubscript_General() { RenderingTest .Create() - .PageSize(500, 300) - + .PageSize(500, 500) .ProduceImages() .ShowResults() .Render(container => @@ -55,28 +54,77 @@ namespace QuestPDF.Examples .Padding(10) .Text(text => { - text.Span("E=mc"); + text.DefaultTextStyle(x => x.FontSize(20)); + text.ParagraphSpacing(2); + + + text.Span("In physics, mass–energy equivalence is the relationship between mass and energy in a system's rest frame, where the two values differ only by a constant and the units of measurement."); + text.Span("[1][2]").Superscript(); + text.Span(" The principle is described by the physicist Albert Einstein's famous formula: E = mc"); text.Span("2").Superscript(); + text.Span(". "); + text.Span("[3]").Superscript(); + + text.EmptyLine(); + + text.Span("H"); + text.Span("2").Subscript(); + text.Span("O is the chemical formula for water, meaning that each of its molecules contains one oxygen and two hydrogen atoms."); 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 SuperscriptSubscript_Effects() + { + RenderingTest + .Create() + .PageSize(800, 400) + .ProduceImages() + .ShowResults() + .Render(container => + { + container + .Padding(25) + .DefaultTextStyle(x => x.FontSize(30)) + .Column(column => + { + column.Spacing(25); + + column.Item().Text(text => + { + text.DefaultTextStyle(x => x.Underline()); + + text.Span("Underline of the superscript (E = mc"); + text.Span("2").Superscript(); + text.Span(") should be at the same height as for normal text."); + }); + + column.Item().Text(text => + { + text.DefaultTextStyle(x => x.Underline()); + + text.Span("Underline of the subscript(H"); + text.Span("2").Subscript(); + text.Span("O) should be slightly lower than a normal text."); + }); + + column.Item().Text(text => + { + text.DefaultTextStyle(x => x.Strikethrough()); + + text.Span("Strikethrough of both superscript (E=mc"); + text.Span("2").Superscript(); + text.Span(") and subscript(H"); + text.Span("2").Subscript(); + text.Span("O) should be visible in the middle of the text."); + }); }); }); } diff --git a/QuestPDF/Drawing/FontManager.cs b/QuestPDF/Drawing/FontManager.cs index 281028f..3c699a9 100644 --- a/QuestPDF/Drawing/FontManager.cs +++ b/QuestPDF/Drawing/FontManager.cs @@ -78,9 +78,14 @@ namespace QuestPDF.Drawing { 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); + // superscript and subscript use slightly bolder font to match visually line thickness + if (style.FontPosition is FontPosition.Superscript or FontPosition.Subscript) + { + var weightValue = (int)weight; + weightValue = Math.Min(weightValue + 100, 1000); + + weight = (SKFontStyleWeight) (weightValue); + } var slant = (style.IsItalic ?? false) ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright; @@ -100,19 +105,22 @@ namespace QuestPDF.Drawing $"1) install the font on your operating system or execution environment. " + $"2) load a font file specifically for QuestPDF usage via the QuestPDF.Drawing.FontManager.RegisterFontType(Stream fileContentStream) static method."); } + + static float GetTextScale(TextStyle style) + { + return style.FontPosition switch + { + FontPosition.Normal => 1f, + FontPosition.Subscript => 0.625f, + FontPosition.Superscript => 0.625f, + _ => throw new ArgumentOutOfRangeException() + }; + } } internal static SKFontMetrics ToFontMetrics(this TextStyle style) { 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 147efcc..26807d3 100644 --- a/QuestPDF/Elements/Text/Items/TextBlockSpan.cs +++ b/QuestPDF/Elements/Text/Items/TextBlockSpan.cs @@ -126,35 +126,47 @@ namespace QuestPDF.Elements.Text.Items { var fontMetrics = Style.ToFontMetrics(); - var glyphOffsetY = GetVerticalGlyphOffsetForStyle(Style); - + var glyphOffset = GetGlyphOffset(); 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, new Position(0, glyphOffsetY), Style); + request.Canvas.DrawText(text, new Position(0, glyphOffset), Style); // draw underline if ((Style.HasUnderline ?? false) && fontMetrics.UnderlinePosition.HasValue) - DrawLine(glyphOffsetY + fontMetrics.UnderlinePosition.Value, fontMetrics.UnderlineThickness ?? 1); + { + var underlineOffset = Style.FontPosition == FontPosition.Superscript ? 0 : glyphOffset; + DrawLine(fontMetrics.UnderlinePosition.Value + underlineOffset, fontMetrics.UnderlineThickness ?? 1); + } // draw stroke if ((Style.HasStrikethrough ?? false) && fontMetrics.StrikeoutPosition.HasValue) - DrawLine(glyphOffsetY + fontMetrics.StrikeoutPosition.Value, fontMetrics.StrikeoutThickness ?? 1); - + { + var strikeoutThickness = fontMetrics.StrikeoutThickness ?? 1; + strikeoutThickness *= Style.FontPosition == FontPosition.Normal ? 1f : 0.625f; + + DrawLine(fontMetrics.StrikeoutPosition.Value + glyphOffset, strikeoutThickness); + } + void DrawLine(float offset, float thickness) { - request.Canvas.DrawRectangle(new Position(0, offset - thickness / 2f), new Size(request.TextSize.Width, thickness), Style.Color); + request.Canvas.DrawRectangle(new Position(0, offset), 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; + float GetGlyphOffset() + { + var fontSize = Style.Size ?? 12f; - return 0; + var offsetFactor = Style.FontPosition switch + { + FontPosition.Normal => 0, + FontPosition.Subscript => 0.1f, + FontPosition.Superscript => -0.35f, + _ => throw new ArgumentOutOfRangeException() + }; + + return fontSize * offsetFactor; + } } } } \ No newline at end of file diff --git a/QuestPDF/Infrastructure/FontPosition.cs b/QuestPDF/Infrastructure/FontPosition.cs index d332740..8ac8131 100644 --- a/QuestPDF/Infrastructure/FontPosition.cs +++ b/QuestPDF/Infrastructure/FontPosition.cs @@ -2,7 +2,7 @@ { internal enum FontPosition { - Normal = 0, + Normal, Subscript, Superscript, }