// 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; } } }