// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using SixLabors.Fonts.Unicode;
namespace SixLabors.Fonts {
///
/// Defines a particular format for text, including font face, size, and style attributes.
/// This class cannot be inherited.
///
public sealed class Font
{
private readonly FontVariation[] variations;
private readonly Lazy metrics;
private readonly Lazy fontName;
///
/// Initializes a new instance of the class.
///
/// The font family.
/// The size of the font in PT units.
public Font(FontFamily family, float size)
: this(family, size, FontStyle.Regular)
{
}
///
/// Initializes a new instance of the class.
///
/// The font family.
/// The size of the font in PT units.
/// The font style.
public Font(FontFamily family, float size, FontStyle style)
{
if (family == default)
{
throw new ArgumentException("Cannot use the default value type instance to create a font.", nameof(family));
}
this.Family = family;
this.RequestedStyle = style;
this.Size = size;
this.variations = [];
this.metrics = new Lazy(this.LoadInstanceInternal, true);
this.fontName = new Lazy(this.LoadFontName, true);
}
///
/// Initializes a new instance of the class.
///
/// The prototype.
/// The font style.
public Font(Font prototype, FontStyle style)
: this(prototype?.Family ?? throw new ArgumentNullException(nameof(prototype)), prototype.Size, style)
{
}
///
/// Initializes a new instance of the class.
///
/// The prototype.
/// The size of the font in PT units.
/// The font style.
public Font(Font prototype, float size, FontStyle style)
: this(prototype?.Family ?? throw new ArgumentNullException(nameof(prototype)), size, style)
{
}
///
/// Initializes a new instance of the class.
///
/// The prototype.
/// The size of the font in PT units.
public Font(Font prototype, float size)
: this(prototype.Family, size, prototype.RequestedStyle)
{
}
///
/// Initializes a new instance of the class with the specified variation axis settings.
///
/// The prototype font providing family, size, and style.
/// The variation axis settings to apply.
public Font(Font prototype, params FontVariation[] variations)
{
Guard.NotNull(prototype, nameof(prototype));
Guard.NotNull(variations, nameof(variations));
this.Family = prototype.Family;
this.RequestedStyle = prototype.RequestedStyle;
this.Size = prototype.Size;
this.variations = variations;
this.metrics = new Lazy(this.LoadInstanceInternal, true);
this.fontName = new Lazy(this.LoadFontName, true);
}
///
/// Gets the family.
///
public FontFamily Family { get; }
///
/// Gets the name.
///
public string Name => this.fontName.Value;
///
/// Gets the size of the font in PT units.
///
public float Size { get; }
///
/// Gets the font metrics.
///
/// Font instance not found.
public FontMetrics FontMetrics => this.metrics.Value ?? throw new FontException("Font instance not found.");
///
/// Gets a value indicating whether this is bold.
///
public bool IsBold => (this.FontMetrics.Description.Style & FontStyle.Bold) == FontStyle.Bold;
///
/// Gets a value indicating whether this is italic.
///
public bool IsItalic => (this.FontMetrics.Description.Style & FontStyle.Italic) == FontStyle.Italic;
///
/// Gets the variation axis settings applied to this font.
///
public ReadOnlySpan Variations => this.variations;
///
/// Gets the requested style.
///
internal FontStyle RequestedStyle { get; }
///
/// Gets the filesystem path to the font family source.
///
///
/// When this method returns, contains the filesystem path to the font family source,
/// if the path exists; otherwise, the default value for the type of the path parameter.
/// This parameter is passed uninitialized.
///
///
/// if the was created via a filesystem path; otherwise, .
///
public bool TryGetPath([NotNullWhen(true)] out string? path)
{
if (this == default)
{
FontsThrowHelper.ThrowDefaultInstance();
}
if (this.FontMetrics is FileFontMetrics fileMetrics)
{
path = fileMetrics.Path;
return true;
}
path = null;
return false;
}
///
/// Gets the glyph for the given codepoint.
///
/// The code point of the character.
///
/// When this method returns, contains the glyph for the given codepoint if the glyph
/// is found; otherwise the default value. This parameter is passed uninitialized.
///
///
/// if the face contains glyphs for the specified codepoint; otherwise, .
///
public bool TryGetGlyphs(CodePoint codePoint, [NotNullWhen(true)] out Glyph? glyph)
=> this.TryGetGlyphs(codePoint, TextAttributes.None, ColorFontSupport.None, out glyph);
///
/// Gets the glyph for the given codepoint.
///
/// The code point of the character.
/// Options for enabling color font support during layout and rendering.
///
/// When this method returns, contains the glyphs for the given codepoint and color support if the glyph
/// is found; otherwise the default value. This parameter is passed uninitialized.
///
///
/// if the face contains glyphs for the specified codepoint; otherwise, .
///
public bool TryGetGlyphs(CodePoint codePoint, ColorFontSupport support, [NotNullWhen(true)] out Glyph? glyph)
=> this.TryGetGlyphs(codePoint, TextAttributes.None, support, out glyph);
///
/// Gets the glyph for the given codepoint.
///
/// The code point of the character.
/// The text attributes to apply to the glyphs.
/// Options for enabling color font support during layout and rendering.
///
/// When this method returns, contains the glyph for the given codepoint, attributes, and color support if the glyph
/// is found; otherwise the default value. This parameter is passed uninitialized.
///
///
/// if the face contains glyphs for the specified codepoint; otherwise, .
///
public bool TryGetGlyphs(
CodePoint codePoint,
TextAttributes textAttributes,
ColorFontSupport support,
[NotNullWhen(true)] out Glyph? glyph)
=> this.TryGetGlyph(codePoint, textAttributes, TextDecorations.None, LayoutMode.HorizontalTopBottom, support, out glyph);
///
/// Gets the glyph for the given codepoint.
///
/// The code point of the character.
/// The text attributes to apply to the glyphs.
/// The layout mode to apply to the glyphs.
/// Options for enabling color font support during layout and rendering.
///
/// When this method returns, contains the glyph for the given codepoint, attributes, and color support if the glyph
/// is found; otherwise the default value. This parameter is passed uninitialized.
///
///
/// if the face contains glyphs for the specified codepoint; otherwise, .
///
public bool TryGetGlyph(
CodePoint codePoint,
TextAttributes textAttributes,
LayoutMode layoutMode,
ColorFontSupport support,
[NotNullWhen(true)] out Glyph? glyph)
=> this.TryGetGlyph(codePoint, textAttributes, TextDecorations.None, layoutMode, support, out glyph);
///
/// Gets the glyph for the given codepoint.
///
/// The code point of the character.
/// The text attributes to apply to the glyphs.
/// The text decorations to apply to the glyphs.
/// The layout mode to apply to the glyphs.
/// Options for enabling color font support during layout and rendering.
///
/// When this method returns, contains the glyph for the given codepoint, attributes, and color support if the glyph
/// is found; otherwise the default value. This parameter is passed uninitialized.
///
///
/// if the face contains glyphs for the specified codepoint; otherwise, .
///
public bool TryGetGlyph(
CodePoint codePoint,
TextAttributes textAttributes,
TextDecorations textDecorations,
LayoutMode layoutMode,
ColorFontSupport support,
[NotNullWhen(true)] out Glyph? glyph)
{
TextRun textRun = new() { Start = 0, End = 1, Font = this, TextAttributes = textAttributes, TextDecorations = textDecorations };
if (this.FontMetrics.TryGetGlyphMetrics(codePoint, textAttributes, textDecorations, layoutMode, support, out FontGlyphMetrics? metrics))
{
glyph = new(metrics.CloneForRendering(textRun), this.Size);
return true;
}
glyph = null;
return false;
}
///
/// Gets the amount, in px units, the glyph should be offset if it is followed by
/// the glyph.
///
/// The current glyph.
/// The next glyph.
/// The DPI (Dots Per Inch) to render/measure the kerning offset at.
///
/// When this method returns, contains the offset, in font units, that should be applied to the
/// glyph, if the offset is found; otherwise the default vector value.
/// This parameter is passed uninitialized.
///
///
/// if the face contains and offset for the glyph combination; otherwise, .
///
public bool TryGetKerningOffset(Glyph current, Glyph next, float dpi, out Vector2 vector)
{
if (this.FontMetrics.TryGetKerningOffset(current.GlyphMetrics.GlyphId, next.GlyphMetrics.GlyphId, out vector))
{
// Scale the result
Vector2 scale = new Vector2(this.Size * dpi) / next.GlyphMetrics.ScaleFactor;
vector *= scale;
return true;
}
return false;
}
private string LoadFontName()
=> this.metrics.Value?.Description.FontName(this.Family.Culture) ?? string.Empty;
private FontMetrics? LoadInstanceInternal()
{
FontMetrics? metrics = this.ResolveBaseMetrics();
if (metrics is null)
{
return null;
}
// If variations are specified and the base metrics supports them, create a variation instance.
if (this.variations.Length > 0)
{
StreamFontMetrics? streamMetrics = metrics switch
{
StreamFontMetrics s => s,
FileFontMetrics f => f.StreamFontMetrics,
_ => null
};
if (streamMetrics is not null)
{
return streamMetrics.CreateVariationInstance(this.variations);
}
}
return metrics;
}
private FontMetrics? ResolveBaseMetrics()
{
if (this.Family.TryGetMetrics(this.RequestedStyle, out FontMetrics? metrics))
{
return metrics;
}
if ((this.RequestedStyle & FontStyle.Italic) == FontStyle.Italic)
{
// Can't find style requested and they want one that's at least partial italic.
// Try the regular italic.
if (this.Family.TryGetMetrics(FontStyle.Italic, out metrics))
{
return metrics;
}
}
if ((this.RequestedStyle & FontStyle.Bold) == FontStyle.Bold)
{
// Can't find style requested and they want one that's at least partial bold.
// Try the regular bold.
if (this.Family.TryGetMetrics(FontStyle.Bold, out metrics))
{
return metrics;
}
}
// Can't find style requested so let's just try returning the default.
ReadOnlySpan styles = this.Family.GetAvailableStyles().Span;
FontStyle defaultStyle = styles[0];
foreach (FontStyle style in styles)
{
if (style == FontStyle.Regular)
{
defaultStyle = FontStyle.Regular;
break;
}
}
this.Family.TryGetMetrics(defaultStyle, out metrics);
return metrics;
}
}
}