Skip to content

Commit

Permalink
Merge pull request #340 from SixLabors/js/fix-composite-glyphs
Browse files Browse the repository at this point in the history
Fix DFKai-SB and KaiU font rendering
  • Loading branch information
JimBobSquarePants authored Jun 29, 2023
2 parents b2396dc + a84df2c commit 7816350
Show file tree
Hide file tree
Showing 23 changed files with 493 additions and 645 deletions.
41 changes: 41 additions & 0 deletions src/SixLabors.Fonts/Bounds.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
// Licensed under the Apache License, Version 2.0.

using System;
using System.Collections.Generic;
using System.Numerics;
using SixLabors.Fonts.Tables.TrueType.Glyphs;

namespace SixLabors.Fonts
{
Expand Down Expand Up @@ -41,6 +43,45 @@ public static Bounds Load(BigEndianBinaryReader reader)
return new Bounds(minX, minY, maxX, maxY);
}

public static Bounds Load(IList<ControlPoint> controlPoints)
{
if (controlPoints is null || controlPoints.Count == 0)
{
return Empty;
}

float xMin = float.MaxValue;
float yMin = float.MaxValue;
float xMax = float.MinValue;
float yMax = float.MinValue;

for (int i = 0; i < controlPoints.Count; i++)
{
Vector2 p = controlPoints[i].Point;
if (p.X < xMin)
{
xMin = p.X;
}

if (p.X > xMax)
{
xMax = p.X;
}

if (p.Y < yMin)
{
yMin = p.Y;
}

if (p.Y > yMax)
{
yMax = p.Y;
}
}

return new Bounds(xMin, yMin, xMax, yMax);
}

public static Bounds Transform(in Bounds bounds, Matrix3x2 matrix)
=> new(Vector2.Transform(bounds.Min, matrix), Vector2.Transform(bounds.Max, matrix));

Expand Down
4 changes: 2 additions & 2 deletions src/SixLabors.Fonts/StreamFontMetrics.TrueType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ internal void ApplyTrueTypeHinting(HintingMode hintingMode, GlyphMetrics metrics
float scaleFactor = pixelSize / this.UnitsPerEm;
this.interpreter.SetControlValueTable(cvt?.ControlValues, scaleFactor, pixelSize, prep?.Instructions);

Bounds bounds = glyphVector.GetBounds();
Bounds bounds = glyphVector.Bounds;

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);
Expand Down Expand Up @@ -131,7 +131,7 @@ private GlyphMetrics CreateTrueTypeGlyphMetrics(
VerticalMetricsTable? vtmx = tables.Vmtx;

GlyphVector vector = glyf.GetGlyph(glyphId);
Bounds bounds = vector.GetBounds();
Bounds bounds = vector.Bounds;
ushort advanceWidth = htmx.GetAdvancedWidth(glyphId);
short lsb = htmx.GetLeftSideBearing(glyphId);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Numerics;
using SixLabors.Fonts.Tables.TrueType;
using SixLabors.Fonts.Tables.TrueType.Glyphs;

namespace SixLabors.Fonts.Tables.AdvancedTypographic.GPos
{
Expand Down Expand Up @@ -127,10 +127,10 @@ public override AnchorXY GetAnchor(FontMetrics fontMetrics, GlyphShapingData dat
break;
}

ReadOnlyMemory<Vector2> points = ttmetric.GetOutline().ControlPoints;
if (this.anchorPointIndex < points.Length)
IList<ControlPoint> points = ttmetric.GetOutline().ControlPoints;
if (this.anchorPointIndex < points.Count)
{
Vector2 point = points.Span[this.anchorPointIndex];
Vector2 point = points[this.anchorPointIndex].Point;
return new((short)point.X, (short)point.Y);
}
}
Expand Down
43 changes: 27 additions & 16 deletions src/SixLabors.Fonts/Tables/TrueType/Glyphs/CompositeGlyphLoader.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
Expand All @@ -10,33 +11,40 @@ namespace SixLabors.Fonts.Tables.TrueType.Glyphs
internal sealed class CompositeGlyphLoader : GlyphLoader
{
private readonly Bounds bounds;
private readonly Composite[] result;
private readonly Composite[] composites;
private readonly ReadOnlyMemory<byte> instructions;

public CompositeGlyphLoader(IEnumerable<Composite> result, Bounds bounds)
public CompositeGlyphLoader(IEnumerable<Composite> composites, Bounds bounds, ReadOnlyMemory<byte> instructions)
{
this.result = result.ToArray();
this.composites = composites.ToArray();
this.bounds = bounds;
this.instructions = instructions;
}

public override GlyphVector CreateGlyph(GlyphTable table)
{
GlyphVector glyph = default;
for (int resultIndex = 0; resultIndex < this.result.Length; resultIndex++)
List<ControlPoint> controlPoints = new();
List<ushort> endPoints = new();
for (int i = 0; i < this.composites.Length; i++)
{
ref Composite composite = ref this.result[resultIndex];
Composite composite = this.composites[i];
var clone = GlyphVector.DeepClone(table.GetGlyph(composite.GlyphIndex));
GlyphVector.TransformInPlace(ref clone, composite.Transformation);
glyph = GlyphVector.Append(glyph, clone, this.bounds);
ushort endPointOffset = (ushort)controlPoints.Count;

controlPoints.AddRange(clone.ControlPoints);
foreach (ushort p in clone.EndPoints)
{
endPoints.Add((ushort)(p + endPointOffset));
}
}

// We ignore any composite glyph instructions and
// instead rely on the individual glyph instructions.
return glyph;
return new(controlPoints, endPoints, this.bounds, this.instructions, true);
}

public static CompositeGlyphLoader LoadCompositeGlyph(BigEndianBinaryReader reader, in Bounds bounds)
{
var result = new List<Composite>();
List<Composite> composites = new();
CompositeGlyphFlags flags;
do
{
Expand Down Expand Up @@ -67,19 +75,19 @@ public static CompositeGlyphLoader LoadCompositeGlyph(BigEndianBinaryReader read
transform.M22 = reader.ReadF2dot14();
}

result.Add(new Composite(glyphIndex, transform));
composites.Add(new Composite(glyphIndex, flags, transform));
}
while ((flags & CompositeGlyphFlags.MoreComponents) != 0);

byte[] instructions = Array.Empty<byte>();
if ((flags & CompositeGlyphFlags.WeHaveInstructions) != 0)
{
// Read the instructions if they exist.
// We don't actually use them though and rely on individual glyph instructions.
ushort instructionSize = reader.ReadUInt16();
reader.ReadUInt8Array(instructionSize);
instructions = reader.ReadUInt8Array(instructionSize);
}

return new CompositeGlyphLoader(result, bounds);
return new CompositeGlyphLoader(composites, bounds, instructions);
}

public static void LoadArguments(BigEndianBinaryReader reader, CompositeGlyphFlags flags, out int dx, out int dy)
Expand Down Expand Up @@ -123,14 +131,17 @@ public static void LoadArguments(BigEndianBinaryReader reader, CompositeGlyphFla

public readonly struct Composite
{
public Composite(ushort glyphIndex, Matrix3x2 transformation)
public Composite(ushort glyphIndex, CompositeGlyphFlags flags, Matrix3x2 transformation)
{
this.GlyphIndex = glyphIndex;
this.Flags = flags;
this.Transformation = transformation;
}

public ushort GlyphIndex { get; }

public CompositeGlyphFlags Flags { get; }

public Matrix3x2 Transformation { get; }
}
}
Expand Down
58 changes: 58 additions & 0 deletions src/SixLabors.Fonts/Tables/TrueType/Glyphs/ControlPoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.

using System;
using System.Numerics;

namespace SixLabors.Fonts.Tables.TrueType.Glyphs
{
/// <summary>
/// Represents a true type glyph control point.
/// </summary>
internal struct ControlPoint : IEquatable<ControlPoint>
{
/// <summary>
/// Gets or sets the position of the point.
/// </summary>
public Vector2 Point;

/// <summary>
/// Gets or sets a value indicating whether the point is on a curve.
/// </summary>
public bool OnCurve;

/// <summary>
/// Initializes a new instance of the <see cref="ControlPoint"/> struct.
/// </summary>
/// <param name="point">The position.</param>
/// <param name="onCurve">Whether the point is on a curve.</param>
public ControlPoint(Vector2 point, bool onCurve)
{
this.Point = point;
this.OnCurve = onCurve;
}

public static bool operator ==(ControlPoint left, ControlPoint right)
=> left.Equals(right);

public static bool operator !=(ControlPoint left, ControlPoint right)
=> !(left == right);

/// <inheritdoc/>
public override bool Equals(object? obj)
=> obj is ControlPoint point && this.Equals(point);

/// <inheritdoc/>
public bool Equals(ControlPoint other)
=> this.Point.Equals(other.Point)
&& this.OnCurve == other.OnCurve;

/// <inheritdoc/>
public override int GetHashCode()
=> HashCode.Combine(this.Point, this.OnCurve);

/// <inheritdoc/>
public override string ToString()
=> FormattableString.Invariant($"Point: {this.Point}, OnCurve: {this.OnCurve}");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public override GlyphVector CreateGlyph(GlyphTable table)
}

this.loop = true;
this.glyph ??= GlyphVector.Empty(table.GetGlyph(0).GetBounds());
this.glyph ??= GlyphVector.Empty(table.GetGlyph(0).Bounds);
return this.glyph.Value;
}
}
Expand Down
45 changes: 0 additions & 45 deletions src/SixLabors.Fonts/Tables/TrueType/Glyphs/GlyphOutline.cs

This file was deleted.

Loading

0 comments on commit 7816350

Please sign in to comment.