Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions src/SixLabors.Fonts/CaretMovement.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.

namespace SixLabors.Fonts;

/// <summary>
/// Specifies a caret movement operation within laid-out text.
/// </summary>
public enum CaretMovement
{
/// <summary>
/// Move to the previous grapheme insertion position.
/// </summary>
Previous,

/// <summary>
/// Move to the next grapheme insertion position.
/// </summary>
Next,

/// <summary>
/// Move to the previous Unicode word boundary.
/// </summary>
PreviousWord,

/// <summary>
/// Move to the next Unicode word boundary.
/// </summary>
NextWord,

/// <summary>
/// Move to the start of the current line.
/// </summary>
LineStart,

/// <summary>
/// Move to the end of the current line.
/// </summary>
LineEnd,

/// <summary>
/// Move to the start of the laid-out text.
/// </summary>
TextStart,

/// <summary>
/// Move to the end of the laid-out text.
/// </summary>
TextEnd,

/// <summary>
/// Move to the previous visual line.
/// </summary>
LineUp,

/// <summary>
/// Move to the next visual line.
/// </summary>
LineDown
}
20 changes: 20 additions & 0 deletions src/SixLabors.Fonts/CaretPlacement.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.

namespace SixLabors.Fonts;

/// <summary>
/// Specifies an absolute caret placement within a laid-out text scope.
/// </summary>
public enum CaretPlacement
{
/// <summary>
/// Place the caret at the start of the laid-out text scope.
/// </summary>
Start,

/// <summary>
/// Place the caret at the end of the laid-out text scope.
/// </summary>
End
}
91 changes: 91 additions & 0 deletions src/SixLabors.Fonts/CaretPosition.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.

using System.Numerics;

namespace SixLabors.Fonts;

/// <summary>
/// Represents a caret line in laid-out text.
/// </summary>
public readonly struct CaretPosition
{
/// <summary>
/// Initializes a new instance of the <see cref="CaretPosition"/> struct.
/// </summary>
/// <param name="lineIndex">The zero-based line index.</param>
/// <param name="graphemeIndex">The grapheme insertion index in the original text.</param>
/// <param name="stringIndex">The UTF-16 index in the original text.</param>
/// <param name="start">The caret start point in pixel units.</param>
/// <param name="end">The caret end point in pixel units.</param>
/// <param name="hasSecondary">Whether the caret has a second visual position.</param>
/// <param name="secondaryStart">The secondary caret start point in pixel units.</param>
/// <param name="secondaryEnd">The secondary caret end point in pixel units.</param>
/// <param name="lineNavigationPosition">The position to preserve when moving between visual lines.</param>
internal CaretPosition(
int lineIndex,
int graphemeIndex,
int stringIndex,
Vector2 start,
Vector2 end,
bool hasSecondary,
Vector2 secondaryStart,
Vector2 secondaryEnd,
float lineNavigationPosition)
{
this.LineIndex = lineIndex;
this.GraphemeIndex = graphemeIndex;
this.StringIndex = stringIndex;
this.Start = start;
this.End = end;
this.HasSecondary = hasSecondary;
this.SecondaryStart = secondaryStart;
this.SecondaryEnd = secondaryEnd;
this.LineNavigationPosition = lineNavigationPosition;
}

/// <summary>
/// Gets the zero-based line index.
/// </summary>
public int LineIndex { get; }

/// <summary>
/// Gets the zero-based grapheme index in the original text.
/// </summary>
public int GraphemeIndex { get; }

/// <summary>
/// Gets the zero-based UTF-16 code unit index in the original text.
/// </summary>
public int StringIndex { get; }

/// <summary>
/// Gets the caret start point in pixel units.
/// </summary>
public Vector2 Start { get; }

/// <summary>
/// Gets the caret end point in pixel units.
/// </summary>
public Vector2 End { get; }

/// <summary>
/// Gets a value indicating whether a second visual caret position is available.
/// </summary>
public bool HasSecondary { get; }

/// <summary>
/// Gets the secondary caret start point in pixel units.
/// </summary>
public Vector2 SecondaryStart { get; }

/// <summary>
/// Gets the secondary caret end point in pixel units.
/// </summary>
public Vector2 SecondaryEnd { get; }

/// <summary>
/// Gets the position to preserve when moving between visual lines.
/// </summary>
internal float LineNavigationPosition { get; }
}
12 changes: 6 additions & 6 deletions src/SixLabors.Fonts/FileFontMetrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ internal override bool TryGetMarkAttachmentClass(ushort glyphId, [NotNullWhen(tr
=> this.fontMetrics.Value.TryGetMarkAttachmentClass(glyphId, out markAttachmentClass);

/// <inheritdoc/>
public override bool TryGetVariationAxes(out VariationAxis[]? variationAxes)
public override bool TryGetVariationAxes(out ReadOnlyMemory<VariationAxis> variationAxes)
=> this.fontMetrics.Value.TryGetVariationAxes(out variationAxes);

/// <inheritdoc/>
Expand All @@ -140,11 +140,11 @@ public override bool TryGetGlyphMetrics(
TextDecorations textDecorations,
LayoutMode layoutMode,
ColorFontSupport support,
[NotNullWhen(true)] out GlyphMetrics? metrics)
[NotNullWhen(true)] out FontGlyphMetrics? metrics)
=> this.fontMetrics.Value.TryGetGlyphMetrics(codePoint, textAttributes, textDecorations, layoutMode, support, out metrics);

/// <inheritdoc />
internal override GlyphMetrics GetGlyphMetrics(
internal override FontGlyphMetrics GetGlyphMetrics(
CodePoint codePoint,
ushort glyphId,
TextAttributes textAttributes,
Expand All @@ -154,7 +154,7 @@ internal override GlyphMetrics GetGlyphMetrics(
=> this.fontMetrics.Value.GetGlyphMetrics(codePoint, glyphId, textAttributes, textDecorations, layoutMode, support);

/// <inheritdoc />
public override IReadOnlyList<CodePoint> GetAvailableCodePoints()
public override ReadOnlyMemory<CodePoint> GetAvailableCodePoints()
=> this.fontMetrics.Value.GetAvailableCodePoints();

/// <inheritdoc/>
Expand Down Expand Up @@ -185,8 +185,8 @@ internal override ReadOnlySpan<float> GetNormalizedCoordinates()
/// Reads a <see cref="StreamFontMetrics"/> from the specified stream.
/// </summary>
/// <param name="path">The file path.</param>
/// <returns>a <see cref="StreamFontMetrics"/>.</returns>
public static FileFontMetrics[] LoadFontCollection(string path)
/// <returns>A read-only memory region containing the font metrics.</returns>
public static ReadOnlyMemory<FileFontMetrics> LoadFontCollection(string path)
{
using FileStream fs = File.OpenRead(path);
long startPos = fs.Position;
Expand Down
16 changes: 11 additions & 5 deletions src/SixLabors.Fonts/Font.cs
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ public bool TryGetGlyph(
[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 GlyphMetrics? metrics))
if (this.FontMetrics.TryGetGlyphMetrics(codePoint, textAttributes, textDecorations, layoutMode, support, out FontGlyphMetrics? metrics))
{
glyph = new(metrics.CloneForRendering(textRun), this.Size);
return true;
Expand Down Expand Up @@ -357,10 +357,16 @@ private string LoadFontName()
}

// Can't find style requested so let's just try returning the default.
IEnumerable<FontStyle>? styles = this.Family.GetAvailableStyles();
FontStyle defaultStyle = styles.Contains(FontStyle.Regular)
? FontStyle.Regular
: styles.First();
ReadOnlySpan<FontStyle> 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;
Expand Down
61 changes: 36 additions & 25 deletions src/SixLabors.Fonts/FontCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,19 +61,19 @@ public FontFamily Add(Stream stream, out FontDescription description)
=> this.AddImpl(stream, CultureInfo.InvariantCulture, out description);

/// <inheritdoc/>
public IEnumerable<FontFamily> AddCollection(string path)
public ReadOnlyMemory<FontFamily> AddCollection(string path)
=> this.AddCollection(path, out _);

/// <inheritdoc/>
public IEnumerable<FontFamily> AddCollection(string path, out IEnumerable<FontDescription> descriptions)
public ReadOnlyMemory<FontFamily> AddCollection(string path, out ReadOnlyMemory<FontDescription> descriptions)
=> this.AddCollectionImpl(path, CultureInfo.InvariantCulture, out descriptions);

/// <inheritdoc/>
public IEnumerable<FontFamily> AddCollection(Stream stream)
public ReadOnlyMemory<FontFamily> AddCollection(Stream stream)
=> this.AddCollection(stream, out _);

/// <inheritdoc/>
public IEnumerable<FontFamily> AddCollection(Stream stream, out IEnumerable<FontDescription> descriptions)
public ReadOnlyMemory<FontFamily> AddCollection(Stream stream, out ReadOnlyMemory<FontDescription> descriptions)
=> this.AddCollectionImpl(stream, CultureInfo.InvariantCulture, out descriptions);

/// <inheritdoc/>
Expand Down Expand Up @@ -101,25 +101,25 @@ public FontFamily AddWithCulture(Stream stream, CultureInfo culture, out FontDes
=> this.AddImpl(stream, culture, out description);

/// <inheritdoc/>
public IEnumerable<FontFamily> AddCollection(string path, CultureInfo culture)
public ReadOnlyMemory<FontFamily> AddCollection(string path, CultureInfo culture)
=> this.AddCollection(path, culture, out _);

/// <inheritdoc/>
public IEnumerable<FontFamily> AddCollection(
public ReadOnlyMemory<FontFamily> AddCollection(
string path,
CultureInfo culture,
out IEnumerable<FontDescription> descriptions)
out ReadOnlyMemory<FontDescription> descriptions)
=> this.AddCollectionImpl(path, culture, out descriptions);

/// <inheritdoc/>
public IEnumerable<FontFamily> AddCollection(Stream stream, CultureInfo culture)
public ReadOnlyMemory<FontFamily> AddCollection(Stream stream, CultureInfo culture)
=> this.AddCollection(stream, culture, out _);

/// <inheritdoc/>
public IEnumerable<FontFamily> AddCollection(
public ReadOnlyMemory<FontFamily> AddCollection(
Stream stream,
CultureInfo culture,
out IEnumerable<FontDescription> descriptions)
out ReadOnlyMemory<FontDescription> descriptions)
=> this.AddCollectionImpl(stream, culture, out descriptions);

/// <inheritdoc/>
Expand Down Expand Up @@ -178,7 +178,7 @@ IEnumerable<FontMetrics> IReadOnlyFontMetricsCollection.GetAllMetrics(string nam
}

/// <inheritdoc/>
IEnumerable<FontStyle> IReadOnlyFontMetricsCollection.GetAllStyles(string name, CultureInfo culture)
ReadOnlyMemory<FontStyle> IReadOnlyFontMetricsCollection.GetAllStyles(string name, CultureInfo culture)
=> ((IReadOnlyFontMetricsCollection)this).GetAllMetrics(name, culture).Select(x => x.Description.Style).ToArray();

/// <inheritdoc/>
Expand Down Expand Up @@ -208,47 +208,58 @@ private FontFamily AddImpl(Stream stream, CultureInfo culture, out FontDescripti
return ((IFontMetricsCollection)this).AddMetrics(metrics, culture);
}

private HashSet<FontFamily> AddCollectionImpl(
private ReadOnlyMemory<FontFamily> AddCollectionImpl(
string path,
CultureInfo culture,
out IEnumerable<FontDescription> descriptions)
out ReadOnlyMemory<FontDescription> descriptions)
{
FileFontMetrics[] fonts = FileFontMetrics.LoadFontCollection(path);
ReadOnlyMemory<FileFontMetrics> fontMetrics = FileFontMetrics.LoadFontCollection(path);
ReadOnlySpan<FileFontMetrics> fonts = fontMetrics.Span;

FontDescription[] description = new FontDescription[fonts.Length];
HashSet<FontFamily> families = [];
FontFamily[] families = new FontFamily[fonts.Length];
int familyCount = 0;
for (int i = 0; i < fonts.Length; i++)
{
description[i] = fonts[i].Description;
FontFamily family = ((IFontMetricsCollection)this).AddMetrics(fonts[i], culture);
families.Add(family);

if (!families.AsSpan(0, familyCount).Contains(family))
{
families[familyCount++] = family;
}
}

descriptions = description;
return families;
return new ReadOnlyMemory<FontFamily>(families, 0, familyCount);
}

private HashSet<FontFamily> AddCollectionImpl(
private ReadOnlyMemory<FontFamily> AddCollectionImpl(
Stream stream,
CultureInfo culture,
out IEnumerable<FontDescription> descriptions)
out ReadOnlyMemory<FontDescription> descriptions)
{
long startPos = stream.Position;
using BigEndianBinaryReader reader = new(stream, true);
TtcHeader ttcHeader = TtcHeader.Read(reader);
List<FontDescription> result = new((int)ttcHeader.NumFonts);
HashSet<FontFamily> installedFamilies = [];
FontDescription[] result = new FontDescription[(int)ttcHeader.NumFonts];
FontFamily[] installedFamilies = new FontFamily[(int)ttcHeader.NumFonts];
int familyCount = 0;
for (int i = 0; i < ttcHeader.NumFonts; ++i)
{
stream.Position = startPos + ttcHeader.OffsetTable[i];
StreamFontMetrics instance = StreamFontMetrics.LoadFont(stream);
installedFamilies.Add(((IFontMetricsCollection)this).AddMetrics(instance, culture));
FontDescription fontDescription = instance.Description;
result.Add(fontDescription);
FontFamily family = ((IFontMetricsCollection)this).AddMetrics(instance, culture);
result[i] = instance.Description;

if (!installedFamilies.AsSpan(0, familyCount).Contains(family))
{
installedFamilies[familyCount++] = family;
}
}

descriptions = result;
return installedFamilies;
return new ReadOnlyMemory<FontFamily>(installedFamilies, 0, familyCount);
}

private FontFamily[] FamiliesByCultureImpl(CultureInfo culture)
Expand Down
Loading
Loading