Skip to content

Commit

Permalink
Merge pull request #295 from SixLabors/js/font-hinting
Browse files Browse the repository at this point in the history
Fix Font Hinting
  • Loading branch information
JimBobSquarePants authored Oct 15, 2022
2 parents 6d831c6 + f704bbd commit 380b668
Show file tree
Hide file tree
Showing 27 changed files with 1,741 additions and 973 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using System;
using System.Runtime.CompilerServices;

namespace SixLabors.Fonts.Unicode
namespace SixLabors.Fonts
{
/// <summary>
/// A helper type for avoiding allocations while building arrays.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace SixLabors.Fonts.Unicode
namespace SixLabors.Fonts
{
/// <summary>
/// ArraySlice represents a contiguous region of arbitrary memory similar
Expand Down Expand Up @@ -64,7 +65,11 @@ public ArraySlice(T[] data, int start, int length)
/// <summary>
/// Gets a <see cref="Span{T}"/> representing this slice.
/// </summary>
public Span<T> Span => new(this.data, this.Start, this.Length);
public Span<T> Span
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new(this.data, this.Start, this.Length);
}

/// <summary>
/// Returns a reference to specified element of the slice.
Expand All @@ -80,8 +85,8 @@ public ref T this[int index]
get
{
DebugGuard.MustBeBetweenOrEqualTo(index, 0, this.Length, nameof(index));
int i = index + this.Start;
return ref this.data[i];
ref T b = ref MemoryMarshal.GetReference(this.Span);
return ref Unsafe.Add(ref b, index);
}
}

Expand Down
19 changes: 11 additions & 8 deletions src/SixLabors.Fonts/Buffer{T}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace SixLabors.Fonts
{
Expand All @@ -13,10 +12,11 @@ namespace SixLabors.Fonts
/// </summary>
/// <typeparam name="T">The type of buffer element.</typeparam>
internal ref struct Buffer<T>
where T : struct
where T : unmanaged
{
private int length;
private readonly byte[] buffer;
private readonly Span<T> span;
private bool isDisposed;

public Buffer(int length)
Expand All @@ -26,21 +26,24 @@ public Buffer(int length)
int bufferSizeInBytes = length * itemSizeBytes;
this.buffer = ArrayPool<byte>.Shared.Rent(bufferSizeInBytes);
this.length = length;

ByteMemoryManager<T> manager = new(this.buffer);
this.Memory = manager.Memory.Slice(0, this.length);
this.span = this.Memory.Span;

this.isDisposed = false;
}

public Memory<T> Memory { get; }

public Span<T> GetSpan()
{
if (this.buffer is null)
{
ThrowObjectDisposedException();
}
#if SUPPORTS_CREATESPAN
ref byte r0 = ref MemoryMarshal.GetReference<byte>(this.buffer);
return MemoryMarshal.CreateSpan(ref Unsafe.As<byte, T>(ref r0), this.length);
#else
return MemoryMarshal.Cast<byte, T>(this.buffer).Slice(0, this.length);
#endif

return this.span;
}

public void Dispose()
Expand Down
51 changes: 51 additions & 0 deletions src/SixLabors.Fonts/ByteMemoryManager{T}.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.

using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace SixLabors.Fonts
{
/// <summary>
/// A custom <see cref="MemoryManager{T}"/> that can wrap <see cref="Memory{T}"/> of <see cref="byte"/> instances
/// and cast them to be <see cref="Memory{T}"/> for any arbitrary unmanaged <typeparamref name="T"/> value type.
/// </summary>
/// <typeparam name="T">The value type to use when casting the wrapped <see cref="Memory{T}"/> instance.</typeparam>
internal sealed class ByteMemoryManager<T> : MemoryManager<T>
where T : unmanaged
{
/// <summary>
/// The wrapped <see cref="Memory{T}"/> of <see cref="byte"/> instance.
/// </summary>
private readonly Memory<byte> memory;

/// <summary>
/// Initializes a new instance of the <see cref="ByteMemoryManager{T}"/> class.
/// </summary>
/// <param name="memory">The <see cref="Memory{T}"/> of <see cref="byte"/> instance to wrap.</param>
public ByteMemoryManager(Memory<byte> memory) => this.memory = memory;

/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
}

/// <inheritdoc/>
public override Span<T> GetSpan() => MemoryMarshal.Cast<byte, T>(this.memory.Span);

/// <inheritdoc/>
public override MemoryHandle Pin(int elementIndex = 0)

// We need to adjust the offset into the wrapped byte segment,
// as the input index refers to the target-cast memory of T.
// We just have to shift this index by the byte size of T.
=> this.memory.Slice(elementIndex * Unsafe.SizeOf<T>()).Pin();

/// <inheritdoc/>
public override void Unpin()
{
}
}
}
4 changes: 2 additions & 2 deletions src/SixLabors.Fonts/GlyphMetrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -210,15 +210,15 @@ internal void RenderDecorationsTo(IGlyphRenderer renderer, Vector2 location, flo

void DrawLine(float thickness, float position)
{
renderer.BeginFigure();

(Vector2 start, Vector2 end, float finalThickness) = GetEnds(thickness, position);

if (finalThickness == 0)
{
return;
}

renderer.BeginFigure();

Vector2 halfHeight = new(0, finalThickness * .5F);

Vector2 tl = start - halfHeight;
Expand Down
2 changes: 2 additions & 0 deletions src/SixLabors.Fonts/GlyphShapingData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,5 +109,7 @@ public GlyphShapingData(GlyphShapingData data, bool clearFeatures = false)
private string DebuggerDisplay
=> FormattableString
.Invariant($" {this.GlyphId} : {this.CodePoint.ToDebuggerDisplay()} : {CodePoint.GetScriptClass(this.CodePoint)} : {this.Direction} : {this.TextRun.TextAttributes} : {this.LigatureId} : {this.LigatureComponent} : {this.IsDecomposed}");

internal string ToDebuggerDisplay() => this.DebuggerDisplay;
}
}
4 changes: 4 additions & 0 deletions src/SixLabors.Fonts/GlyphSubstitutionCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using SixLabors.Fonts.Tables.AdvancedTypographic;
Expand Down Expand Up @@ -299,6 +300,7 @@ public void Replace(int index, ReadOnlySpan<ushort> glyphIds)
}
}

[DebuggerDisplay("{DebuggerDisplay,nq}")]
private class OffsetGlyphDataPair
{
public OffsetGlyphDataPair(int offset, GlyphShapingData data)
Expand All @@ -310,6 +312,8 @@ public OffsetGlyphDataPair(int offset, GlyphShapingData data)
public int Offset { get; set; }

public GlyphShapingData Data { get; set; }

private string DebuggerDisplay => FormattableString.Invariant($"Offset: {this.Offset}, Data: {this.Data.ToDebuggerDisplay()}");
}
}
}
9 changes: 2 additions & 7 deletions src/SixLabors.Fonts/HintingMode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,8 @@ public enum HintingMode
None,

/// <summary>
/// Hint the glyphs in a vertical direction only. <see href="http://agg.sourceforge.net/antigrain.com/research/font_rasterization/"/>.
/// Hint the glyph using standard configuration.
/// </summary>
HintY,

/// <summary>
/// Hint the glyphs in both directions.
/// </summary>
HintXY
Standard
}
}
2 changes: 1 addition & 1 deletion src/SixLabors.Fonts/KerningMode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public enum KerningMode
/// <summary>
/// Specifies that kerning is applied.
/// </summary>
Normal,
Standard,

/// <summary>
/// Specifies that kerning is not applied.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using System;
using System.Runtime.CompilerServices;

namespace SixLabors.Fonts.Unicode
namespace SixLabors.Fonts
{
/// <summary>
/// Provides a mapped view of an underlying slice, selecting arbitrary indices
Expand All @@ -14,15 +14,15 @@ namespace SixLabors.Fonts.Unicode
internal readonly struct MappedArraySlice<T>
where T : struct
{
private readonly ReadOnlyArraySlice<T> data;
private readonly ReadOnlyArraySlice<int> map;
private readonly ArraySlice<T> data;
private readonly ArraySlice<int> map;

/// <summary>
/// Initializes a new instance of the <see cref="MappedArraySlice{T}"/> struct.
/// </summary>
/// <param name="data">The data slice.</param>
/// <param name="map">The map slice.</param>
public MappedArraySlice(in ReadOnlyArraySlice<T> data, in ReadOnlyArraySlice<int> map)
public MappedArraySlice(in ArraySlice<T> data, in ArraySlice<int> map)
{
Guard.MustBeGreaterThanOrEqualTo(data.Length, map.Length, nameof(map));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace SixLabors.Fonts.Unicode
namespace SixLabors.Fonts
{
/// <summary>
/// ReadOnlyArraySlice represents a contiguous region of arbitrary memory similar
Expand Down Expand Up @@ -64,7 +65,11 @@ public ReadOnlyArraySlice(T[] data, int start, int length)
/// <summary>
/// Gets a <see cref="Span{T}"/> representing this slice.
/// </summary>
public ReadOnlySpan<T> Span => new(this.data, this.Start, this.Length);
public ReadOnlySpan<T> Span
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => new(this.data, this.Start, this.Length);
}

/// <summary>
/// Returns a reference to specified element of the slice.
Expand All @@ -74,14 +79,14 @@ public ReadOnlyArraySlice(T[] data, int start, int length)
/// <exception cref="IndexOutOfRangeException">
/// Thrown when index less than 0 or index greater than or equal to <see cref="Length"/>.
/// </exception>
public readonly ref T this[int index]
public readonly T this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
DebugGuard.MustBeBetweenOrEqualTo(index, 0, this.Length, nameof(index));
int i = index + this.Start;
return ref this.data[i];
ref T b = ref MemoryMarshal.GetReference(this.Span);
return Unsafe.Add(ref b, index);
}
}

Expand Down
52 changes: 52 additions & 0 deletions src/SixLabors.Fonts/ReadOnlyMappedArraySlice{T}.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.

using System;
using System.Runtime.CompilerServices;

namespace SixLabors.Fonts
{
/// <summary>
/// Provides a readonly mapped view of an underlying slice, selecting arbitrary indices
/// from the source array.
/// </summary>
/// <typeparam name="T">The type of item contained in the underlying array.</typeparam>
internal readonly struct ReadonlyMappedArraySlice<T>
where T : struct
{
private readonly ReadOnlyArraySlice<T> data;
private readonly ReadOnlyArraySlice<int> map;

/// <summary>
/// Initializes a new instance of the <see cref="ReadonlyMappedArraySlice{T}"/> struct.
/// </summary>
/// <param name="data">The data slice.</param>
/// <param name="map">The map slice.</param>
public ReadonlyMappedArraySlice(in ReadOnlyArraySlice<T> data, in ReadOnlyArraySlice<int> map)
{
Guard.MustBeGreaterThanOrEqualTo(data.Length, map.Length, nameof(map));

this.data = data;
this.map = map;
}

/// <summary>
/// Gets the number of items in the map.
/// </summary>
public int Length => this.map.Length;

/// <summary>
/// Returns a reference to specified element of the slice.
/// </summary>
/// <param name="index">The index of the element to return.</param>
/// <returns>The <typeparamref name="T"/>.</returns>
/// <exception cref="IndexOutOfRangeException">
/// Thrown when index less than 0 or index greater than or equal to <see cref="Length"/>.
/// </exception>
public readonly T this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.data[this.map[index]];
}
}
}
14 changes: 7 additions & 7 deletions src/SixLabors.Fonts/StreamFontMetrics.TrueType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ internal partial class StreamFontMetrics
[ThreadStatic]
private TrueTypeInterpreter? interpreter;

internal void ApplyTrueTypeHinting(HintingMode hintingMode, GlyphMetrics metrics, ref GlyphVector glyphVector, Vector2 scaleXY, float scaledPPEM)
internal void ApplyTrueTypeHinting(HintingMode hintingMode, GlyphMetrics metrics, ref GlyphVector glyphVector, Vector2 scaleXY, float pixelSize)
{
if (hintingMode == HintingMode.None || this.outlineType != OutlineType.TrueType)
{
Expand All @@ -51,15 +51,15 @@ internal void ApplyTrueTypeHinting(HintingMode hintingMode, GlyphMetrics metrics

CvtTable? cvt = tables.Cvt;
PrepTable? prep = tables.Prep;
float scaleFactor = scaledPPEM / this.UnitsPerEm;
this.interpreter.SetControlValueTable(cvt?.ControlValues, scaleFactor, scaledPPEM, prep?.Instructions);
float scaleFactor = pixelSize / this.UnitsPerEm;
this.interpreter.SetControlValueTable(cvt?.ControlValues, scaleFactor, pixelSize, prep?.Instructions);

Bounds bounds = glyphVector.GetBounds();

var pp1 = new Vector2(bounds.Min.X - (metrics.LeftSideBearing * scaleXY.X), 0);
var pp2 = new Vector2(pp1.X + (metrics.AdvanceWidth * scaleXY.X), 0);
var pp3 = new Vector2(0, bounds.Max.Y + (metrics.TopSideBearing * scaleXY.Y));
var pp4 = new Vector2(0, pp3.Y - (metrics.AdvanceHeight * scaleXY.Y));
Vector2 pp1 = new(MathF.Round(bounds.Min.X - (metrics.LeftSideBearing * scaleXY.X)), 0);
Vector2 pp2 = new(MathF.Round(pp1.X + (metrics.AdvanceWidth * scaleXY.X)), 0);
Vector2 pp3 = new(0, MathF.Round(bounds.Max.Y + (metrics.TopSideBearing * scaleXY.Y)));
Vector2 pp4 = new(0, MathF.Round(pp3.Y - (metrics.AdvanceHeight * scaleXY.Y)));

GlyphVector.Hint(hintingMode, ref glyphVector, this.interpreter, pp1, pp2, pp3, pp4);
}
Expand Down
2 changes: 1 addition & 1 deletion src/SixLabors.Fonts/Tables/Cff/RefStack{T}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace SixLabors.Fonts.Tables.Cff
/// </summary>
/// <typeparam name="T">The type of elements in the stack.</typeparam>
internal ref struct RefStack<T>
where T : struct
where T : unmanaged
{
private const int MaxLength = 0X7FFFFFC7;
private Buffer<T> buffer;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ public override GlyphVector CreateGlyph(GlyphTable table)
for (int resultIndex = 0; resultIndex < this.result.Length; resultIndex++)
{
ref Composite composite = ref this.result[resultIndex];
glyph = GlyphVector.Append(glyph, GlyphVector.Transform(GlyphVector.DeepClone(table.GetGlyph(composite.GlyphIndex)), composite.Transformation), this.bounds);
var clone = GlyphVector.DeepClone(table.GetGlyph(composite.GlyphIndex));
GlyphVector.TransformInPlace(ref clone, composite.Transformation);
glyph = GlyphVector.Append(glyph, clone, this.bounds);
}

// We ignore any composite glyph instructions and
Expand Down
Loading

0 comments on commit 380b668

Please sign in to comment.