Skip to content

Commit 3da4cc9

Browse files
aespitiaNeilMacMullenChristoph WagnerChristoph-Wagnerswharden
authored
Fonts: Extend styling options (weight, slant, density, etc.) (ScottPlot#5013)
* Add IsAboveSeries to PolarAxis to allow its grid data to be drawn above the chart in a radar. * Allow specifying values for fonts explicitly, while keeping bool bold/italic shims for compatibility. * Revert "Add IsAboveSeries to PolarAxis to allow its grid data to be drawn above the chart in a radar." This reverts commit 7177134. * primitives * updating references * adding cookbook recipe * formatting updates * moving fontExtensions out of primitives * Update CHANGELOG.md * Cookbook: add more font styling examples --------- Co-authored-by: Neil MacMullen <neil.macmullen@outlook.com> Co-authored-by: Christoph Wagner <gitlab@cwagner.me> Co-authored-by: Christoph Wagner <Christoph-Wagner@users.noreply.github.com> Co-authored-by: Scott W Harden <swharden@gmail.com>
1 parent 972ccab commit 3da4cc9

16 files changed

Lines changed: 274 additions & 33 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ _Not yet on NuGet..._
1818
* Drawing: Add `FillPath()` and `FillCircle()` and deprecate draw overloads that accept a `FillStyle` (#4987) @CoderPM2011
1919
* GitHub: Create a `devconainer.json` file to facilitate development in GitHub Codespaces (#5002) @oxygen-dioxide
2020
* Maui: Improve behavior for interactive plots when their user input processor is disabled (#4990, #4989) @Adam--
21+
* Fonts: Added new styling options for weight, slant, density, etc. (#5013, #4873) @aespitia @Christoph-Wagner
2122

2223
## ScottPlot 5.0.55
2324
_Published on [NuGet](https://www.nuget.org/profiles/ScottPlot) on 2025-03-22_

src/ScottPlot5/ScottPlot5 Cookbook/Recipes/General/Styling.cs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using SkiaSharp;
2+
13
namespace ScottPlotCookbook.Recipes.Introduction;
24

35
public class Styling : ICategory
@@ -351,4 +353,72 @@ public override void Execute()
351353
myPlot.Axes.Title.FullFigureCenter = true;
352354
}
353355
}
356+
357+
public class SetFontName : RecipeBase
358+
{
359+
public override string Name => "Set Font by Name";
360+
public override string Description => "Set font by its name to apply it to common plot components.";
361+
362+
[Test]
363+
public override void Execute()
364+
{
365+
myPlot.Font.Set("Comic Sans MS");
366+
myPlot.Title("Hello, World");
367+
var sig = myPlot.Add.Signal(Generate.Sin(51, mult: 1e6));
368+
sig.LegendText = "Hello, Custom Font";
369+
}
370+
}
371+
372+
public class SetFontWeight : RecipeBase
373+
{
374+
public override string Name => "Set Font Weight";
375+
public override string Description => "Font weight can be customized.";
376+
377+
[Test]
378+
public override void Execute()
379+
{
380+
myPlot.Font.Set("Calibri"); // apply to many existing plot labels
381+
myPlot.Title("Hello, World");
382+
383+
FontWeight[] weights = [FontWeight.Light, FontWeight.Normal,
384+
FontWeight.SemiBold, FontWeight.Bold, FontWeight.ExtraBlack];
385+
386+
for (int i = 0; i < weights.Length; i++)
387+
{
388+
FontWeight weight = weights[i];
389+
myPlot.Font.Set("Calibri", weight: weight); // apply to new labels
390+
var text = myPlot.Add.Text($"FontWeight.{weight}", 0, i);
391+
text.LabelFontSize = 24;
392+
}
393+
394+
myPlot.Axes.SetLimits(-1, 5, -2, weights.Length);
395+
myPlot.HideGrid();
396+
}
397+
}
398+
399+
public class SetFontSlant : RecipeBase
400+
{
401+
public override string Name => "Set Font Slant";
402+
public override string Description => "Font slant can be customized.";
403+
404+
[Test]
405+
public override void Execute()
406+
{
407+
myPlot.Font.Set("Calibri", slant: FontSlant.Italic); // apply to many existing plot labels
408+
myPlot.Title("Hello, World");
409+
410+
FontSlant[] slants = [FontSlant.Upright, FontSlant.Italic, FontSlant.Oblique];
411+
412+
for (int i = 0; i < slants.Length; i++)
413+
{
414+
FontSlant slant = slants[i];
415+
myPlot.Font.Set("Calibri", slant: slant); // apply to new labels
416+
var text = myPlot.Add.Text($"FontSlant.{slant}", 0, i);
417+
text.LabelFontSize = 24;
418+
}
419+
420+
myPlot.Axes.SetLimits(-1, 5, -1, slants.Length);
421+
myPlot.HideGrid();
422+
}
423+
}
354424
}

src/ScottPlot5/ScottPlot5/DataSourceUtilities.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,8 @@ public static DataPoint GetNearestFast(IDataSource dataSource, Coordinates mouse
410410
searchedLeft = NextPoint;
411411
else
412412
searchedRight = NextPoint;
413-
};
413+
}
414+
;
414415

415416
if (closestDistanceSquared <= maxDistanceSquared)
416417
{
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
namespace ScottPlot
2+
{
3+
public static class FontExtensions
4+
{
5+
public static SKFontStyleWidth ToSKFontStyleWidth(this FontSpacing width)
6+
{
7+
return width switch
8+
{
9+
FontSpacing.UltraCondensed => SKFontStyleWidth.UltraCondensed,
10+
FontSpacing.ExtraCondensed => SKFontStyleWidth.ExtraCondensed,
11+
FontSpacing.Condensed => SKFontStyleWidth.Condensed,
12+
FontSpacing.SemiCondensed => SKFontStyleWidth.SemiCondensed,
13+
FontSpacing.Normal => SKFontStyleWidth.Normal,
14+
FontSpacing.SemiExpanded => SKFontStyleWidth.SemiExpanded,
15+
FontSpacing.Expanded => SKFontStyleWidth.Expanded,
16+
FontSpacing.ExtraExpanded => SKFontStyleWidth.ExtraExpanded,
17+
FontSpacing.UltraExpanded => SKFontStyleWidth.UltraExpanded,
18+
_ => throw new ArgumentOutOfRangeException(nameof(width))
19+
};
20+
}
21+
22+
public static SKFontStyleSlant ToSKFontStyleSlant(this FontSlant slant)
23+
{
24+
return slant switch
25+
{
26+
FontSlant.Upright => SKFontStyleSlant.Upright,
27+
FontSlant.Italic => SKFontStyleSlant.Italic,
28+
FontSlant.Oblique => SKFontStyleSlant.Oblique,
29+
_ => throw new ArgumentOutOfRangeException(nameof(slant))
30+
};
31+
}
32+
33+
public static SKFontStyleWeight ToSKFontStyleWeight(this FontWeight weight)
34+
{
35+
return weight switch
36+
{
37+
FontWeight.Invisible => SKFontStyleWeight.Invisible,
38+
FontWeight.Thin => SKFontStyleWeight.Thin,
39+
FontWeight.ExtraLight => SKFontStyleWeight.ExtraLight,
40+
FontWeight.Light => SKFontStyleWeight.Light,
41+
FontWeight.Normal => SKFontStyleWeight.Normal,
42+
FontWeight.Medium => SKFontStyleWeight.Medium,
43+
FontWeight.SemiBold => SKFontStyleWeight.SemiBold,
44+
FontWeight.Bold => SKFontStyleWeight.Bold,
45+
FontWeight.ExtraBold => SKFontStyleWeight.ExtraBold,
46+
FontWeight.Black => SKFontStyleWeight.Black,
47+
FontWeight.ExtraBlack => SKFontStyleWeight.ExtraBlack,
48+
_ => throw new ArgumentOutOfRangeException(nameof(weight))
49+
};
50+
}
51+
}
52+
}

src/ScottPlot5/ScottPlot5/FontResolvers/FileFontResolver.cs

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,52 @@ namespace ScottPlot.FontResolvers;
33
/// <summary>
44
/// Font resolver that creates a typeface from a specified TTF file
55
/// </summary>
6-
public class FileFontResolver(string name, string path, bool bold, bool italic) : IFontResolver
6+
public class FileFontResolver(string name, string path, FontWeight weight, FontSlant slant, FontSpacing width) : IFontResolver
77
{
8+
9+
[Obsolete("Explicitly declare SkFontStyles")]
10+
public FileFontResolver(string name, string path, bool bold, bool italic) : this(name, path,
11+
bold ? FontWeight.Bold : FontWeight.Normal,
12+
italic ? FontSlant.Italic : FontSlant.Upright, FontSpacing.Normal)
13+
{ }
14+
815
private string FontName { get; } = name;
916
private string FontPath { get; } = File.Exists(path)
1017
? Path.GetFullPath(path)
1118
: throw new FileNotFoundException(path);
12-
private bool Bold { get; } = bold;
13-
private bool Italic { get; } = italic;
19+
private FontWeight Weight { get; } = weight;
20+
private FontSlant Slant { get; } = slant;
21+
private FontSpacing Width { get; } = width;
1422

1523
/// <summary>
1624
/// Attempt to create the typeface using the given settings.
1725
/// Returns null if this resolver does not match the requested font.
1826
/// </summary>
27+
public SKTypeface? CreateTypeface(string fontName, FontWeight weight, FontSlant slant, FontSpacing width)
28+
{
29+
return (FontName == fontName) && (Weight == weight) && (Slant == slant) && (Width == width)
30+
? SKTypeface.FromFile(FontPath)
31+
: null;
32+
}
33+
1934
public SKTypeface? CreateTypeface(string fontName, bool bold, bool italic)
2035
{
21-
return (FontName == fontName) && (Bold == bold) && (Italic == italic)
22-
? SKTypeface.FromFile(FontPath)
23-
: null;
36+
return CreateTypeface(fontName,
37+
bold ? FontWeight.Bold : FontWeight.Normal,
38+
italic ? FontSlant.Italic : FontSlant.Upright,
39+
FontSpacing.Normal);
40+
}
41+
42+
public SKTypeface? CreateTypeface(string fontName, bool bold, bool italic, FontSpacing width = FontSpacing.Normal)
43+
{
44+
return CreateTypeface(fontName,
45+
bold ? FontWeight.Bold : FontWeight.Normal,
46+
italic ? FontSlant.Italic : FontSlant.Upright,
47+
width);
2448
}
2549

2650
public override string ToString()
2751
{
28-
return $"File font resolver: name='{FontName}', bold={Bold}, italic={Italic}";
52+
return $"File font resolver: name='{FontName}', weight={Weight}, slant={Slant}, width={Width}";
2953
}
3054
}

src/ScottPlot5/ScottPlot5/FontResolvers/SystemFontResolver.cs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
namespace ScottPlot.FontResolvers;
32

43
public class SystemFontResolver : IFontResolver
@@ -10,7 +9,7 @@ private static HashSet<string> GetInstalledFonts()
109

1110
internal static string InstalledSansFont()
1211
{
13-
// Prefer the the system default because it is probably the best for international users
12+
// Prefer the system default because it is probably the best for international users
1413
// https://github.com/ScottPlot/ScottPlot/issues/2746
1514
string font = SKTypeface.Default.FamilyName;
1615

@@ -70,16 +69,29 @@ public static SKTypeface CreateDefaultTypeface()
7069
: typeface;
7170
}
7271

73-
public SKTypeface? CreateTypeface(string fontName, bool bold, bool italic)
72+
public SKTypeface? CreateTypeface(string fontName, FontWeight weight, FontSlant slant, FontSpacing width)
7473
{
7574
if (!GetInstalledFonts().Contains(fontName))
7675
return null;
7776

78-
SKFontStyleWeight weight = bold ? SKFontStyleWeight.Bold : SKFontStyleWeight.Normal;
79-
SKFontStyleSlant slant = italic ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright;
80-
SKFontStyleWidth width = SKFontStyleWidth.Normal;
81-
SKFontStyle style = new(weight, width, slant);
77+
SKFontStyle style = new(weight.ToSKFontStyleWeight(), width.ToSKFontStyleWidth(), slant.ToSKFontStyleSlant());
8278
SKTypeface? typeface = SKTypeface.FromFamilyName(fontName, style);
8379
return typeface;
8480
}
81+
82+
public SKTypeface? CreateTypeface(string fontName, bool bold, bool italic)
83+
{
84+
return CreateTypeface(fontName,
85+
bold ? FontWeight.Bold : FontWeight.Normal,
86+
italic ? FontSlant.Italic : FontSlant.Upright,
87+
FontSpacing.Normal);
88+
}
89+
90+
public SKTypeface? CreateTypeface(string fontName, bool bold, bool italic, FontSpacing width = FontSpacing.Normal)
91+
{
92+
return CreateTypeface(fontName,
93+
bold ? FontWeight.Bold : FontWeight.Normal,
94+
italic ? FontSlant.Italic : FontSlant.Upright,
95+
width);
96+
}
8597
}

src/ScottPlot5/ScottPlot5/Fonts.cs

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public static class Fonts
1212
* https://github.com/ScottPlot/ScottPlot/issues/2833
1313
* https://github.com/ScottPlot/ScottPlot/pull/2848
1414
*/
15-
private static readonly ConcurrentDictionary<(string, bool, bool), SKTypeface> TypefaceCache = [];
15+
private static readonly ConcurrentDictionary<(string, FontWeight, FontSlant, FontSpacing), SKTypeface> TypefaceCache = [];
1616

1717
/// <summary>
1818
/// Collection of font resolvers that return typefaces from font names and style information
@@ -24,14 +24,21 @@ public static class Fonts
2424
/// </summary>
2525
public static void AddFontFile(string name, string path, bool bold = false, bool italic = false)
2626
{
27-
FontResolvers.FileFontResolver resolver = new(name, path, bold, italic);
27+
FontWeight weight = bold ? FontWeight.Bold : FontWeight.Normal;
28+
FontSlant slant = italic ? FontSlant.Italic : FontSlant.Upright;
29+
FontSpacing width = FontSpacing.Normal;
30+
FontResolvers.FileFontResolver resolver = new(name, path, weight, slant, width);
2831
FontResolvers.Add(resolver);
2932
}
3033

3134
/// <summary>
3235
/// This font is used for almost all text rendering.
3336
/// </summary>
3437
public static string Default { get; set; } = SystemFontResolver.InstalledSansFont();
38+
public static FontWeight? DefaultWeight { get; set; }
39+
public static FontSlant? DefaultSlant { get; set; }
40+
public static FontSpacing? DefaultWidth { get; set; }
41+
public static SKTypeface? DefaultFontStyle { get; set; }
3542

3643
/// <summary>
3744
/// Name of a sans-serif font present on the system
@@ -70,9 +77,9 @@ public static bool Exists(string fontName, bool bold, bool italic)
7077
/// A cached typeface will be used if it exists,
7178
/// otherwise one will be created, cached, and returned.
7279
/// </summary>
73-
public static SKTypeface GetTypeface(string fontName, bool bold, bool italic)
80+
public static SKTypeface GetTypeface(string fontName, FontWeight weight, FontSlant slant, FontSpacing spacing)
7481
{
75-
var typefaceCacheKey = (fontName, bold, italic);
82+
var typefaceCacheKey = (fontName, weight, slant, spacing);
7683

7784
if (TypefaceCache.TryGetValue(typefaceCacheKey, out SKTypeface? cachedTypeface))
7885
{
@@ -82,7 +89,7 @@ public static SKTypeface GetTypeface(string fontName, bool bold, bool italic)
8289

8390
foreach (IFontResolver resolver in FontResolvers)
8491
{
85-
SKTypeface? resolvedTypeface = resolver.CreateTypeface(fontName, bold, italic);
92+
SKTypeface? resolvedTypeface = resolver.CreateTypeface(fontName, weight, slant, spacing);
8693
if (resolvedTypeface is not null)
8794
{
8895
TypefaceCache.TryAdd(typefaceCacheKey, resolvedTypeface);
@@ -95,6 +102,26 @@ public static SKTypeface GetTypeface(string fontName, bool bold, bool italic)
95102
return defaultTypeface;
96103
}
97104

105+
/// <summary>
106+
/// Returns a typeface for the requested font name and style.
107+
/// A cached typeface will be used if it exists,
108+
/// otherwise one will be created, cached, and returned.
109+
/// </summary>
110+
public static SKTypeface GetTypeface(string fontName, bool bold, bool italic)
111+
{
112+
FontWeight weight = bold ? FontWeight.Bold : FontWeight.Normal;
113+
FontSlant slant = italic ? FontSlant.Italic : FontSlant.Upright;
114+
FontSpacing width = FontSpacing.Normal;
115+
return GetTypeface(fontName, weight, slant, width);
116+
}
117+
118+
public static SKTypeface GetTypeface(string fontName, bool bold, bool italic, FontSpacing width)
119+
{
120+
FontWeight weight = bold ? FontWeight.Bold : FontWeight.Normal;
121+
FontSlant slant = italic ? FontSlant.Italic : FontSlant.Upright;
122+
return GetTypeface(fontName, weight, slant, width);
123+
}
124+
98125
#region Font Detection
99126

100127
/// <summary>

src/ScottPlot5/ScottPlot5/Interfaces/IFontResolver.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ namespace ScottPlot;
55
/// </summary>
66
public interface IFontResolver
77
{
8+
9+
/// <summary>
10+
/// Returns a new instance to a typeface with exactly specified styles
11+
/// </summary>
12+
SKTypeface? CreateTypeface(string fontName, FontWeight weight, FontSlant slant, FontSpacing spacing);
13+
814
/// <summary>
915
/// Returns a new instance to a typeface that most closely matches the requested family name and style
1016
/// </summary>

src/ScottPlot5/ScottPlot5/Plottables/TINPlot.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ private void DrawContours(RenderPack rp, SKPaint paint)
6363
{
6464
Pixel[] pixels = line.Path.Points.Select(Axes.GetPixel).ToArray();
6565
Drawing.DrawLines(rp.Canvas, paint, pixels, ContourLineStyle);
66-
};
66+
}
67+
;
6768
}
6869

6970
public virtual void Render(RenderPack rp)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace ScottPlot;
2+
3+
public enum FontSlant
4+
{
5+
Upright = 0,
6+
Italic = 1,
7+
Oblique = 2
8+
}

0 commit comments

Comments
 (0)