Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add parsing anchor table format 2 #238

Merged
merged 8 commits into from
Feb 26, 2022
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
2 changes: 1 addition & 1 deletion samples/DrawWithImageSharp/DrawWithImageSharp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta13.15" />
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta14" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
</ItemGroup>

Expand Down
1 change: 0 additions & 1 deletion samples/DrawWithImageSharp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ public static void Main(string[] args)
RenderText(fontWoff2, "\uFB01", pointSize: 11.25F);
RenderText(tahoma, "p", pointSize: 11.25F);
RenderText(tahoma, "Lorem ipsum dolor sit amet", pointSize: 11.25F);
return;
RenderText(uiFont, "Soft\u00ADHyphen", pointSize: 72);
FontFamily bugzilla = fonts.Add(@"Fonts\me_quran_volt_newmet.ttf");

Expand Down
34 changes: 24 additions & 10 deletions src/SixLabors.Fonts/GlyphPositioningCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,31 @@ internal sealed class GlyphPositioningCollection : IGlyphShapingCollection
/// <summary>
/// Initializes a new instance of the <see cref="GlyphPositioningCollection"/> class.
/// </summary>
/// <param name="mode">The text layout mode.</param>
public GlyphPositioningCollection(LayoutMode mode) => this.IsVerticalLayoutMode = mode.IsVertical();
/// <param name="options">The text options.</param>
public GlyphPositioningCollection(TextOptions options)
{
this.IsVerticalLayoutMode = options.LayoutMode.IsVertical();
this.ColorFontSupport = options.ColorFontSupport;
this.ApplyHinting = options.ApplyHinting;
}

/// <inheritdoc />
public int Count => this.offsets.Count;

/// <inheritdoc />
public bool IsVerticalLayoutMode { get; }

/// <summary>
/// Gets a value indicating whether to enable various color font formats.
/// </summary>
public ColorFontSupport ColorFontSupport { get; }

/// <summary>
/// Gets a value indicating whether to apply hinting - The use of mathematical instructions
/// to adjust the display of an outline font so that it lines up with a rasterized grid.
/// </summary>
public bool ApplyHinting { get; }

/// <inheritdoc />
public ReadOnlySpan<ushort> this[int index] => this.glyphs[index].GlyphIds;

Expand All @@ -55,8 +71,7 @@ public void AddShapingFeature(int index, TagEntry feature)
/// <inheritdoc />
public void EnableShapingFeature(int index, Tag feature)
{
List<TagEntry> features = this.glyphs[index].Features;
foreach (TagEntry tagEntry in features)
foreach (TagEntry tagEntry in this.glyphs[index].Features)
{
if (tagEntry.Tag == feature)
{
Expand Down Expand Up @@ -86,13 +101,12 @@ public bool TryGetGlyphMetricsAtOffset(int offset, [NotNullWhen(true)] out Glyph
/// </summary>
/// <param name="fontMetrics">The font face with metrics.</param>
/// <param name="collection">The glyph substitution collection.</param>
/// <param name="options">The renderer options.</param>
/// <returns><see langword="true"/> if the metrics collection does not contain any fallbacks; otherwise <see langword="false"/>.</returns>
public bool TryAddOrUpdate(FontMetrics fontMetrics, GlyphSubstitutionCollection collection, TextOptions options)
public bool TryAddOrUpdate(FontMetrics fontMetrics, GlyphSubstitutionCollection collection)
{
if (this.Count == 0)
{
return this.Add(fontMetrics, collection, options);
return this.Add(fontMetrics, collection);
}

bool hasFallBacks = false;
Expand Down Expand Up @@ -123,7 +137,7 @@ public bool TryAddOrUpdate(FontMetrics fontMetrics, GlyphSubstitutionCollection
{
// Perform a semi-deep clone (FontMetrics is not cloned) so we can continue to
// cache the original in the font metrics and only update our collection.
foreach (GlyphMetrics gm in fontMetrics.GetGlyphMetrics(codePoint, id, options.ColorFontSupport))
foreach (GlyphMetrics gm in fontMetrics.GetGlyphMetrics(codePoint, id, this.ColorFontSupport))
{
if (gm.GlyphType == GlyphType.Fallback && !CodePoint.IsControl(codePoint))
{
Expand Down Expand Up @@ -158,7 +172,7 @@ public bool TryAddOrUpdate(FontMetrics fontMetrics, GlyphSubstitutionCollection
return !hasFallBacks;
}

private bool Add(FontMetrics fontMetrics, GlyphSubstitutionCollection collection, TextOptions options)
private bool Add(FontMetrics fontMetrics, GlyphSubstitutionCollection collection)
{
bool hasFallBacks = false;
for (int i = 0; i < collection.Count; i++)
Expand All @@ -172,7 +186,7 @@ private bool Add(FontMetrics fontMetrics, GlyphSubstitutionCollection collection
{
// Perform a semi-deep clone (FontMetrics is not cloned) so we can continue to
// cache the original in the font metrics and only update our collection.
foreach (GlyphMetrics gm in fontMetrics.GetGlyphMetrics(codePoint, id, options.ColorFontSupport))
foreach (GlyphMetrics gm in fontMetrics.GetGlyphMetrics(codePoint, id, this.ColorFontSupport))
{
if (gm.GlyphType == GlyphType.Fallback && !CodePoint.IsControl(codePoint))
{
Expand Down
2 changes: 1 addition & 1 deletion src/SixLabors.Fonts/IGlyphShapingCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ internal interface IGlyphShapingCollection
/// <summary>
/// Gets a value indicating whether the text layout mode is vertical.
/// </summary>
public bool IsVerticalLayoutMode { get; }
bool IsVerticalLayoutMode { get; }

/// <summary>
/// Gets the glyph ids at the specified index.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,21 +222,22 @@ public static bool CheckAllCoverages(
}

public static void ApplyAnchor(
FontMetrics fontMetrics,
GlyphPositioningCollection collection,
ushort index,
AnchorTable baseAnchor,
MarkRecord markRecord,
int baseGlyphIndex)
{
short baseX = baseAnchor.XCoordinate;
short baseY = baseAnchor.YCoordinate;
short markX = markRecord.MarkAnchorTable.XCoordinate;
short markY = markRecord.MarkAnchorTable.YCoordinate;

GlyphShapingData data = collection.GetGlyphShapingData(index);
data.Bounds.X = baseX - markX;
data.Bounds.Y = baseY - markY;
data.MarkAttachment = baseGlyphIndex;
GlyphShapingData baseData = collection.GetGlyphShapingData(baseGlyphIndex);
AnchorXY baseXY = baseAnchor.GetAnchor(fontMetrics, baseData, collection);

GlyphShapingData markData = collection.GetGlyphShapingData(index);
AnchorXY markXY = markRecord.MarkAnchorTable.GetAnchor(fontMetrics, markData, collection);

markData.Bounds.X = baseXY.XCoordinate - markXY.XCoordinate;
markData.Bounds.Y = baseXY.YCoordinate - markXY.YCoordinate;
markData.MarkAttachment = baseGlyphIndex;
}

public static void ApplyPosition(
Expand Down
110 changes: 105 additions & 5 deletions src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/AnchorTable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Numerics;

namespace SixLabors.Fonts.Tables.AdvancedTypographic.GPos
{
[DebuggerDisplay("X: {XCoordinate}, Y: {YCoordinate}")]
internal class AnchorTable
internal abstract class AnchorTable
{
/// <summary>
/// Initializes a new instance of the <see cref="AnchorTable"/> class.
/// </summary>
/// <param name="xCoordinate">The horizontal value, in design units.</param>
/// <param name="yCoordinate">The vertical value, in design units.</param>
public AnchorTable(short xCoordinate, short yCoordinate)
protected AnchorTable(short xCoordinate, short yCoordinate)
{
this.XCoordinate = xCoordinate;
this.YCoordinate = yCoordinate;
Expand All @@ -24,12 +25,14 @@ public AnchorTable(short xCoordinate, short yCoordinate)
/// <summary>
/// Gets the horizontal value, in design units.
/// </summary>
public short XCoordinate { get; }
protected short XCoordinate { get; }

/// <summary>
/// Gets the vertical value, in design units.
/// </summary>
public short YCoordinate { get; }
protected short YCoordinate { get; }

public abstract AnchorXY GetAnchor(FontMetrics fontMetrics, GlyphShapingData data, GlyphPositioningCollection collection);

/// <summary>
/// Loads the anchor table.
Expand All @@ -45,7 +48,9 @@ public static AnchorTable Load(BigEndianBinaryReader reader, long offset)
return anchorFormat switch
{
1 => AnchorFormat1.Load(reader),
_ => throw new NotSupportedException($"anchorFormat {anchorFormat} not supported. Should be '1'.")
2 => AnchorFormat2.Load(reader),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As somebody who knows nothing about fonts, making these error messages more descriptive might be helpful too. If I knew that my font format wasn't supported, I'd just pick a different one.

 3 => throw new NotSupportedException($"Variable fonts (format 3) are not supported. Read: https://docs.sixlabors.com/articles/fonts/anchorTableFormats.html")
 _ => throw new NotSupportedException($"Fonts using anchor table format {anchorFormat} are not supported. Read: https://docs.sixlabors.com/articles/fonts/anchorTableFormats.html")

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't link to docs from exception messages. The maintenance burden of tracking URL changes would be too much.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's fair enough, was just a thought

3 => AnchorFormat3.Load(reader),
_ => throw new InvalidFontFileException($"anchorFormat identifier {anchorFormat} is invalid. Should be '1', '2' or '3'.")
};
}

Expand All @@ -71,6 +76,101 @@ public static AnchorFormat1 Load(BigEndianBinaryReader reader)
short yCoordinate = reader.ReadInt16();
return new AnchorFormat1(xCoordinate, yCoordinate);
}

public override AnchorXY GetAnchor(FontMetrics fontMetrics, GlyphShapingData data, GlyphPositioningCollection collection)
=> new(this.XCoordinate, this.YCoordinate);
}

internal sealed class AnchorFormat2 : AnchorTable
{
private readonly ushort anchorPointIndex;

public AnchorFormat2(short xCoordinate, short yCoordinate, ushort anchorPointIndex)
: base(xCoordinate, yCoordinate) => this.anchorPointIndex = anchorPointIndex;

public static AnchorFormat2 Load(BigEndianBinaryReader reader)
{
// +--------------+------------------------+------------------------------------------------+
// | Type | Name | Description |
// +==============+========================+================================================+
// | uint16 | anchorFormat | Format identifier, = 2 |
// +--------------+------------------------+------------------------------------------------+
// | int16 | xCoordinate | Horizontal value, in design units. |
// +--------------+------------------------+------------------------------------------------+
// | int16 | yCoordinate | Vertical value, in design units. |
// +--------------+------------------------+------------------------------------------------+
// | uint16 + anchorPoint | Index to glyph contour point. +
// +--------------+------------------------+------------------------------------------------+
short xCoordinate = reader.ReadInt16();
short yCoordinate = reader.ReadInt16();
ushort anchorPointIndex = reader.ReadUInt16();
return new AnchorFormat2(xCoordinate, yCoordinate, anchorPointIndex);
}

public override AnchorXY GetAnchor(FontMetrics fontMetrics, GlyphShapingData data, GlyphPositioningCollection collection)
{
if (collection.ApplyHinting)
{
foreach (GlyphMetrics metric in fontMetrics.GetGlyphMetrics(data.CodePoint, collection.ColorFontSupport))
{
ReadOnlyMemory<Vector2> points = metric.GetOutline().ControlPoints;
if (this.anchorPointIndex < points.Length)
{
Vector2 point = points.Span[this.anchorPointIndex];
return new((short)point.X, (short)point.Y);
}
}
}

return new(this.XCoordinate, this.YCoordinate);
}
}

internal sealed class AnchorFormat3 : AnchorTable
{
// TODO: actually use the xDeviceOffset.
private readonly ushort xDeviceOffset;

// TODO: actually use the yDeviceOffset.
private readonly ushort yDeviceOffset;

public AnchorFormat3(short xCoordinate, short yCoordinate, ushort xDeviceOffset, ushort yDeviceOffset)
: base(xCoordinate, yCoordinate)
{
this.xDeviceOffset = xDeviceOffset;
this.yDeviceOffset = yDeviceOffset;
}

public static AnchorFormat3 Load(BigEndianBinaryReader reader)
{
// +--------------+------------------------+-----------------------------------------------------------+
// | Type | Name | Description |
// +==============+========================+===========================================================+
// | uint16 | anchorFormat | Format identifier, = 3 |
// +--------------+------------------------+-----------------------------------------------------------+
// | int16 | xCoordinate | Horizontal value, in design units. |
// +--------------+------------------------+-----------------------------------------------------------+
// | int16 | yCoordinate | Vertical value, in design units. |
// +--------------+------------------------+-----------------------------------------------------------+
// | uint16 + anchorPoint | Index to glyph contour point. +
// +--------------+------------------------+-----------------------------------------------------------+
// | Offset16 | xDeviceOffset + Offset to Device table (non-variable font) / |
// | | | VariationIndex table (variable font) for X coordinate, |
// | | | from beginning of Anchor table (may be NULL) |
// +--------------+------------------------+-----------------------------------------------------------+
// | Offset16 | yDeviceOffset + Offset to Device table (non-variable font) / |
// | | | VariationIndex table (variable font) for Y coordinate, |
// | | | from beginning of Anchor table (may be NULL) |
// +--------------+------------------------+-----------------------------------------------------------+
short xCoordinate = reader.ReadInt16();
short yCoordinate = reader.ReadInt16();
ushort xDeviceOffset = reader.ReadOffset16();
ushort yDeviceOffset = reader.ReadOffset16();
return new AnchorFormat3(xCoordinate, yCoordinate, xDeviceOffset, yDeviceOffset);
}

public override AnchorXY GetAnchor(FontMetrics fontMetrics, GlyphShapingData data, GlyphPositioningCollection collection)
=> new(this.XCoordinate, this.YCoordinate);
}
}
}
27 changes: 27 additions & 0 deletions src/SixLabors.Fonts/Tables/AdvancedTypographic/GPos/AnchorXY.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.

namespace SixLabors.Fonts.Tables.AdvancedTypographic.GPos
{
/// <summary>
/// Represents the anchor coordinates for a given table.
/// </summary>
internal readonly struct AnchorXY
{
public AnchorXY(short x, short y)
{
this.XCoordinate = x;
this.YCoordinate = y;
}

/// <summary>
/// Gets the horizontal value, in design units.
/// </summary>
public short XCoordinate { get; }

/// <summary>
/// Gets the vertical value, in design units.
/// </summary>
public short YCoordinate { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -130,28 +130,54 @@ public override bool TryUpdatePosition(
GlyphShapingData current = collection.GetGlyphShapingData(index);
GlyphShapingData next = collection.GetGlyphShapingData(nextIndex);

// TODO: Vertical.
if (current.Direction == TextDirection.LeftToRight)
{
current.Bounds.Width = exit.XCoordinate + current.Bounds.X;
AnchorXY exitXY = exit.GetAnchor(fontMetrics, current, collection);
AnchorXY entryXY = entry.GetAnchor(fontMetrics, next, collection);

int delta = entry.XCoordinate + next.Bounds.X;
next.Bounds.Width -= delta;
next.Bounds.X -= delta;
if (!collection.IsVerticalLayoutMode)
{
// Horizontal
if (current.Direction == TextDirection.LeftToRight)
{
current.Bounds.Width = exitXY.XCoordinate + current.Bounds.X;

int delta = entryXY.XCoordinate + next.Bounds.X;
next.Bounds.Width -= delta;
next.Bounds.X -= delta;
}
else
{
int delta = exitXY.XCoordinate + current.Bounds.X;
current.Bounds.Width -= delta;
current.Bounds.X -= delta;

next.Bounds.Width = entryXY.XCoordinate + next.Bounds.X;
}
}
else
{
int delta = exit.XCoordinate + current.Bounds.X;
current.Bounds.Width -= delta;
current.Bounds.X -= delta;

next.Bounds.Width = entry.XCoordinate + next.Bounds.X;
// Vertical : Top to bottom
if (current.Direction == TextDirection.LeftToRight)
{
current.Bounds.Height = exitXY.YCoordinate + current.Bounds.Y;

int delta = entryXY.YCoordinate + next.Bounds.Y;
next.Bounds.Height -= delta;
next.Bounds.Y -= delta;
}
else
{
int delta = exitXY.YCoordinate + current.Bounds.Y;
current.Bounds.Height -= delta;
current.Bounds.Y -= delta;

next.Bounds.Height = entryXY.YCoordinate + next.Bounds.Y;
}
}

int child = index;
int parent = nextIndex;
int xOffset = entry.XCoordinate - exit.XCoordinate;
int yOffset = entry.YCoordinate - exit.YCoordinate;
int xOffset = entryXY.XCoordinate - exitXY.XCoordinate;
int yOffset = entryXY.YCoordinate - exitXY.YCoordinate;
if (this.LookupFlags.HasFlag(LookupFlags.RightToLeft))
{
int temp = child;
Expand Down
Loading