Browse Source

Improvement text style handling (#660)

* Text: added support for fake italic (oblique)

* Text: added support for take bold

* Text: added fallback when font does not specify underline or strikeout configurations
Marcin Ziąbek 2 years ago
parent
commit
4e74bf6ab9

+ 60 - 4
Source/QuestPDF/Drawing/FontManager.cs

@@ -20,7 +20,7 @@ namespace QuestPDF.Drawing
     public static class FontManager
     public static class FontManager
     {
     {
         private static readonly ConcurrentDictionary<string, FontStyleSet> StyleSets = new();
         private static readonly ConcurrentDictionary<string, FontStyleSet> StyleSets = new();
-        private static readonly ConcurrentDictionary<TextStyle, SKFontMetrics> FontMetrics = new();
+        private static readonly ConcurrentDictionary<TextStyle, FontMetrics> FontMetrics = new();
         private static readonly ConcurrentDictionary<TextStyle, SKPaint> FontPaints = new();
         private static readonly ConcurrentDictionary<TextStyle, SKPaint> FontPaints = new();
         private static readonly ConcurrentDictionary<string, SKPaint> ColorPaints = new();
         private static readonly ConcurrentDictionary<string, SKPaint> ColorPaints = new();
         private static readonly ConcurrentDictionary<TextStyle, Font> ShaperFonts = new();
         private static readonly ConcurrentDictionary<TextStyle, Font> ShaperFonts = new();
@@ -140,12 +140,16 @@ namespace QuestPDF.Drawing
 
 
             static SKPaint Convert(TextStyle style)
             static SKPaint Convert(TextStyle style)
             {
             {
+                var targetTypeface = GetTypeface(style);
+                
                 return new SKPaint
                 return new SKPaint
                 {
                 {
                     Color = SKColor.Parse(style.Color),
                     Color = SKColor.Parse(style.Color),
-                    Typeface = GetTypeface(style),
+                    Typeface = targetTypeface,
                     TextSize = (style.Size ?? 12) * GetTextScale(style),
                     TextSize = (style.Size ?? 12) * GetTextScale(style),
                     IsAntialias = true,
                     IsAntialias = true,
+                    TextSkewX = GetTextSkew(style, targetTypeface),
+                    FakeBoldText = UseFakeBoldText(style, targetTypeface)
                 };
                 };
             }
             }
 
 
@@ -194,11 +198,63 @@ namespace QuestPDF.Drawing
                     _ => throw new ArgumentOutOfRangeException()
                     _ => throw new ArgumentOutOfRangeException()
                 };
                 };
             }
             }
+
+            static float GetTextSkew(TextStyle originalTextStyle, SKTypeface targetTypeface)
+            {
+                // requested italic text but got typeface that is not italic
+                var useObliqueText = originalTextStyle.IsItalic == true && !targetTypeface.IsItalic;
+                
+                return useObliqueText ? -0.25f : 0;
+            }
+            
+            static bool UseFakeBoldText(TextStyle originalTextStyle, SKTypeface targetTypeface)
+            {
+                // requested bold text but got typeface that is not bold
+                return originalTextStyle.FontWeight > FontWeight.Medium && !targetTypeface.IsBold;
+            }
         }
         }
 
 
-        internal static SKFontMetrics ToFontMetrics(this TextStyle style)
+        internal static FontMetrics ToFontMetrics(this TextStyle style)
         {
         {
-            return FontMetrics.GetOrAdd(style, key => key.NormalPosition().ToPaint().FontMetrics);
+            return FontMetrics.GetOrAdd(style, key =>
+            {
+                var skiaFontMetrics = key.NormalPosition().ToPaint().FontMetrics;
+                
+                return new FontMetrics
+                {
+                    Ascent = skiaFontMetrics.Ascent,
+                    Descent = skiaFontMetrics.Descent,
+                    
+                    UnderlineThickness = GetUnderlineThickness(),
+                    UnderlinePosition = GetUnderlinePosition(),
+                    
+                    StrikeoutThickness = GetStrikeoutThickness(),
+                    StrikeoutPosition = GetStrikeoutPosition()
+                };
+
+                // HACK: On MacOS, certain font metrics are not determined accurately.
+                // Provide defaults based on other metrics.
+                
+                float GetUnderlineThickness()
+                {
+                    return skiaFontMetrics.UnderlineThickness ?? (skiaFontMetrics.XHeight * 0.1f);
+                }
+                
+                float GetUnderlinePosition()
+                {
+                    return skiaFontMetrics.UnderlinePosition ?? (skiaFontMetrics.XHeight * 0.2f);
+                }
+                
+                float GetStrikeoutThickness()
+                {
+                    return skiaFontMetrics.StrikeoutThickness ?? (skiaFontMetrics.XHeight * 0.1f);
+                }
+                
+                float GetStrikeoutPosition()
+                {
+                    return skiaFontMetrics.StrikeoutPosition ?? (-skiaFontMetrics.XHeight * 0.5f);
+                }
+            });
         }
         }
 
 
         internal static Font ToShaperFont(this TextStyle style)
         internal static Font ToShaperFont(this TextStyle style)

+ 5 - 5
Source/QuestPDF/Elements/Text/Items/TextBlockSpan.cs

@@ -145,19 +145,19 @@ namespace QuestPDF.Elements.Text.Items
                 request.Canvas.DrawText(textDrawingCommand.Value.SkTextBlob, new Position(textDrawingCommand.Value.TextOffsetX, glyphOffsetY), Style);
                 request.Canvas.DrawText(textDrawingCommand.Value.SkTextBlob, new Position(textDrawingCommand.Value.TextOffsetX, glyphOffsetY), Style);
 
 
             // draw underline
             // draw underline
-            if ((Style.HasUnderline ?? false) && fontMetrics.UnderlinePosition.HasValue)
+            if (Style.HasUnderline ?? false)
             {
             {
                 var underlineOffset = Style.FontPosition == FontPosition.Superscript ? 0 : glyphOffsetY;
                 var underlineOffset = Style.FontPosition == FontPosition.Superscript ? 0 : glyphOffsetY;
-                DrawLine(fontMetrics.UnderlinePosition.Value + underlineOffset, fontMetrics.UnderlineThickness ?? 1);
+                DrawLine(fontMetrics.UnderlinePosition + underlineOffset, fontMetrics.UnderlineThickness);
             }
             }
             
             
             // draw stroke
             // draw stroke
-            if ((Style.HasStrikethrough ?? false) && fontMetrics.StrikeoutPosition.HasValue)
+            if (Style.HasStrikethrough ?? false)
             {
             {
-                var strikeoutThickness = fontMetrics.StrikeoutThickness ?? 1;
+                var strikeoutThickness = fontMetrics.StrikeoutThickness;
                 strikeoutThickness *= Style.FontPosition == FontPosition.Normal ? 1f : 0.625f;
                 strikeoutThickness *= Style.FontPosition == FontPosition.Normal ? 1f : 0.625f;
                 
                 
-                DrawLine(fontMetrics.StrikeoutPosition.Value + glyphOffsetY, strikeoutThickness);
+                DrawLine(fontMetrics.StrikeoutPosition + glyphOffsetY, strikeoutThickness);
             }
             }
             
             
             void DrawLine(float offset, float thickness)
             void DrawLine(float offset, float thickness)

+ 13 - 0
Source/QuestPDF/Infrastructure/FontMetrics.cs

@@ -0,0 +1,13 @@
+namespace QuestPDF.Infrastructure;
+
+internal struct FontMetrics
+{
+    public float Ascent { get; set; }
+    public float Descent { get; set; }
+    
+    public float UnderlinePosition { get; set; }
+    public float UnderlineThickness { get; set; }
+    
+    public float StrikeoutPosition { get; set; }
+    public float StrikeoutThickness { get; set; }
+}