Skip to content

Commit 9aa2a5c

Browse files
Merge pull request #464 from SixLabors/js/issue-462
Support COLRv1 and SVG Fonts
2 parents 7eeb133 + 81bbe24 commit 9aa2a5c

File tree

161 files changed

+8365
-960
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

161 files changed

+8365
-960
lines changed

SixLabors.Fonts.sln

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Microsoft Visual Studio Solution File, Format Version 12.00
2-
# Visual Studio Version 17
3-
VisualStudioVersion = 17.2.32519.379
2+
# Visual Studio Version 18
3+
VisualStudioVersion = 18.0.11012.119
44
MinimumVisualStudioVersion = 10.0.40219.1
55
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_root", "_root", "{C317F1B1-D75E-4C6D-83EB-80367343E0D7}"
66
ProjectSection(SolutionItems) = preProject
@@ -73,8 +73,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UnicodeTestData", "UnicodeT
7373
EndProject
7474
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SixLabors.Fonts.Benchmarks", "tests\SixLabors.Fonts.Benchmarks\SixLabors.Fonts.Benchmarks\SixLabors.Fonts.Benchmarks.csproj", "{FB8FDC5F-7FEB-4132-9133-C25E05C0B3D9}"
7575
EndProject
76-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DrawWithImageSharp", "samples\DrawWithImageSharp\DrawWithImageSharp.csproj", "{3D3F6164-6DE9-433F-8B20-61A40F53F343}"
77-
EndProject
7876
Global
7977
GlobalSection(SolutionConfigurationPlatforms) = preSolution
8078
Debug|Any CPU = Debug|Any CPU
@@ -101,10 +99,6 @@ Global
10199
{FB8FDC5F-7FEB-4132-9133-C25E05C0B3D9}.Debug|Any CPU.Build.0 = Debug|Any CPU
102100
{FB8FDC5F-7FEB-4132-9133-C25E05C0B3D9}.Release|Any CPU.ActiveCfg = Release|Any CPU
103101
{FB8FDC5F-7FEB-4132-9133-C25E05C0B3D9}.Release|Any CPU.Build.0 = Release|Any CPU
104-
{3D3F6164-6DE9-433F-8B20-61A40F53F343}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
105-
{3D3F6164-6DE9-433F-8B20-61A40F53F343}.Debug|Any CPU.Build.0 = Debug|Any CPU
106-
{3D3F6164-6DE9-433F-8B20-61A40F53F343}.Release|Any CPU.ActiveCfg = Release|Any CPU
107-
{3D3F6164-6DE9-433F-8B20-61A40F53F343}.Release|Any CPU.Build.0 = Release|Any CPU
108102
EndGlobalSection
109103
GlobalSection(SolutionProperties) = preSolution
110104
HideSolutionNode = FALSE
@@ -119,7 +113,6 @@ Global
119113
{ABB6E111-672F-4846-88D6-C49C6CD01606} = {249327CF-1415-428B-8EEA-8C7705B1DE8F}
120114
{654DD381-B93D-4459-B669-296F5D9172ED} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC}
121115
{FB8FDC5F-7FEB-4132-9133-C25E05C0B3D9} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC}
122-
{3D3F6164-6DE9-433F-8B20-61A40F53F343} = {71A3911C-D6B9-4EBE-9691-2FE28BDF462E}
123116
EndGlobalSection
124117
GlobalSection(ExtensibilityGlobals) = postSolution
125118
SolutionGuid = {38F4B47F-4F74-40F5-8707-C0EF1D0BDF92}

samples/DrawWithImageSharp/BoundingBoxes.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.Numerics;
55
using SixLabors.Fonts;
6+
using SixLabors.Fonts.Rendering;
67
using SixLabors.ImageSharp;
78
using SixLabors.ImageSharp.Drawing;
89
using SixLabors.ImageSharp.Drawing.Processing;

samples/DrawWithImageSharp/CustomGlyphBuilder.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.Numerics;
55
using SixLabors.Fonts;
6+
using SixLabors.Fonts.Rendering;
67
using SixLabors.ImageSharp.Drawing;
78
using SixLabors.ImageSharp.Drawing.Text;
89

@@ -13,7 +14,7 @@ namespace DrawWithImageSharp;
1314
/// </summary>
1415
internal class CustomGlyphBuilder : GlyphBuilder
1516
{
16-
private readonly List<FontRectangle> glyphBounds = new();
17+
private readonly List<FontRectangle> glyphBounds = [];
1718

1819
public CustomGlyphBuilder()
1920
{

samples/DrawWithImageSharp/DrawWithImageSharp.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
</Content>
4242
</ItemGroup>
4343

44-
<ItemGroup>
44+
<ItemGroup>
4545
<ProjectReference Include="..\..\src\SixLabors.Fonts\SixLabors.Fonts.csproj" />
4646
</ItemGroup>
4747

samples/DrawWithImageSharp/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ public static void RenderTextProcessorWithAlignment(
318318
new SolidBrush(Color.Yellow),
319319
null)));
320320

321-
img[size.Width / 2, size.Height / 2] = Color.White;
321+
img[size.Width / 2, size.Height / 2] = Color.White.ToPixel<Rgba32>();
322322

323323
string h = ha.ToString().Replace(nameof(HorizontalAlignment), string.Empty).ToLower();
324324
string v = va.ToString().Replace(nameof(VerticalAlignment), string.Empty).ToLower();

samples/DrawWithImageSharp/TextAlignmentSample.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.Numerics;
55
using SixLabors.Fonts;
6+
using SixLabors.Fonts.Rendering;
67
using SixLabors.ImageSharp;
78
using SixLabors.ImageSharp.Drawing;
89
using SixLabors.ImageSharp.Drawing.Processing;
@@ -15,7 +16,7 @@ public static class TextAlignmentSample
1516
{
1617
public static void Generate(Font font)
1718
{
18-
using var img = new Image<Rgba32>(1000, 1000);
19+
using Image<Rgba32> img = new(1000, 1000);
1920
img.Mutate(x => x.Fill(Color.White));
2021

2122
foreach (VerticalAlignment v in Enum.GetValues(typeof(VerticalAlignment)))
@@ -63,9 +64,9 @@ public static void Draw(Image<Rgba32> img, Font font, VerticalAlignment vert, Ho
6364
break;
6465
}
6566

66-
var glyphBuilder = new CustomGlyphBuilder();
67+
CustomGlyphBuilder glyphBuilder = new();
6768

68-
var renderer = new TextRenderer(glyphBuilder);
69+
TextRenderer renderer = new(glyphBuilder);
6970

7071
TextOptions textOptions = new(font)
7172
{
@@ -82,8 +83,7 @@ public static void Draw(Image<Rgba32> img, Font font, VerticalAlignment vert, Ho
8283
IEnumerable<IPath> shapesToDraw = glyphBuilder.Paths;
8384
img.Mutate(x => x.Fill(Color.Black, glyphBuilder.Paths));
8485

85-
Rgba32 f = Color.Fuchsia;
86-
f.A = 128;
86+
Color f = Color.Fuchsia.WithAlpha(.5F);
8787
img.Mutate(x => x.Fill(Color.Black, glyphBuilder.Paths));
8888
img.Mutate(x => x.Draw(f, 1, glyphBuilder.Boxes));
8989
img.Mutate(x => x.Draw(Color.Lime, 1, glyphBuilder.TextBox));

samples/DrawWithImageSharp/TextAlignmentWrapped.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.Numerics;
55
using SixLabors.Fonts;
6+
using SixLabors.Fonts.Rendering;
67
using SixLabors.ImageSharp;
78
using SixLabors.ImageSharp.Drawing;
89
using SixLabors.ImageSharp.Drawing.Processing;
@@ -18,7 +19,7 @@ public static void Generate(Font font)
1819
const int wrappingWidth = 400;
1920
const int size = (wrappingWidth + (wrappingWidth / 3)) * 3;
2021

21-
using var img = new Image<Rgba32>(size, size);
22+
using Image<Rgba32> img = new(size, size);
2223
img.Mutate(x => x.Fill(Color.White));
2324

2425
foreach (VerticalAlignment v in Enum.GetValues(typeof(VerticalAlignment)))
@@ -66,9 +67,9 @@ public static void Draw(Image<Rgba32> img, Font font, VerticalAlignment vert, Ho
6667
break;
6768
}
6869

69-
var glyphBuilder = new CustomGlyphBuilder();
70+
CustomGlyphBuilder glyphBuilder = new();
7071

71-
var renderer = new TextRenderer(glyphBuilder);
72+
TextRenderer renderer = new(glyphBuilder);
7273

7374
TextOptions textOptions = new(font)
7475
{
@@ -85,8 +86,7 @@ public static void Draw(Image<Rgba32> img, Font font, VerticalAlignment vert, Ho
8586
IEnumerable<IPath> shapesToDraw = glyphBuilder.Paths;
8687
img.Mutate(x => x.Fill(Color.Black, glyphBuilder.Paths));
8788

88-
Rgba32 f = Color.Fuchsia;
89-
f.A = 128;
89+
Color f = Color.Fuchsia.WithAlpha(.5F);
9090
img.Mutate(x => x.Fill(Color.Black, glyphBuilder.Paths));
9191
img.Mutate(x => x.Draw(f, 1, glyphBuilder.Boxes));
9292
img.Mutate(x => x.Draw(Color.Lime, 1, glyphBuilder.TextBox));

src/SixLabors.Fonts/BigEndianBinaryReader.cs

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
namespace SixLabors.Fonts;
1010

1111
/// <summary>
12-
/// BinaryReader using big-endian encoding.
12+
/// A binary reader that reads in big-endian format.
1313
/// </summary>
1414
[DebuggerDisplay("Start: {StartOfStream}, Position: {BaseStream.Position}")]
1515
internal sealed class BigEndianBinaryReader : IDisposable
@@ -19,6 +19,7 @@ internal sealed class BigEndianBinaryReader : IDisposable
1919
/// </summary>
2020
private readonly byte[] buffer = new byte[16];
2121

22+
private readonly long startOfStream;
2223
private readonly bool leaveOpen;
2324

2425
/// <summary>
@@ -31,12 +32,10 @@ internal sealed class BigEndianBinaryReader : IDisposable
3132
public BigEndianBinaryReader(Stream stream, bool leaveOpen)
3233
{
3334
this.BaseStream = stream;
34-
this.StartOfStream = stream.Position;
35+
this.startOfStream = stream.Position;
3536
this.leaveOpen = leaveOpen;
3637
}
3738

38-
private long StartOfStream { get; }
39-
4039
/// <summary>
4140
/// Gets the underlying stream of the EndianBinaryReader.
4241
/// </summary>
@@ -52,7 +51,7 @@ public void Seek(long offset, SeekOrigin origin)
5251
// If SeekOrigin.Begin, the offset will be set to the start of stream position.
5352
if (origin == SeekOrigin.Begin)
5453
{
55-
offset += this.StartOfStream;
54+
offset += this.startOfStream;
5655
}
5756

5857
this.BaseStream.Seek(offset, origin);
@@ -68,6 +67,13 @@ public byte ReadByte()
6867
return this.buffer[0];
6968
}
7069

70+
public TEnum ReadByte<TEnum>()
71+
where TEnum : struct, Enum
72+
{
73+
TryConvert(this.ReadByte(), out TEnum value);
74+
return value;
75+
}
76+
7177
/// <summary>
7278
/// Reads a single signed byte from the stream.
7379
/// </summary>
@@ -78,9 +84,9 @@ public sbyte ReadSByte()
7884
return unchecked((sbyte)this.buffer[0]);
7985
}
8086

81-
public float ReadF2dot14()
87+
public float ReadF2Dot14()
8288
{
83-
const float f2Dot14ToFloat = 16384.0f;
89+
const float f2Dot14ToFloat = 16384F;
8490
return this.ReadInt16() / f2Dot14ToFloat;
8591
}
8692

@@ -103,28 +109,35 @@ public TEnum ReadInt16<TEnum>()
103109
return value;
104110
}
105111

112+
/// <summary>
113+
/// Reads a signed 16-bit integer in big-endian order, representing an FWORD value from the current stream position.
114+
/// </summary>
115+
/// <returns>A 16-bit signed integer read from the stream, interpreted as an FWORD value.</returns>
106116
public short ReadFWORD() => this.ReadInt16();
107117

108118
public short[] ReadFWORDArray(int length) => this.ReadInt16Array(length);
109119

120+
/// <summary>
121+
/// Reads an unsigned 16-bit integer (UFWORD) from the current stream and advances the position by two bytes.
122+
/// </summary>
123+
/// <returns>An unsigned 16-bit integer read from the current stream.</returns>
110124
public ushort ReadUFWORD() => this.ReadUInt16();
111125

112126
/// <summary>
113-
/// Reads a fixed 32-bit value from the stream.
114-
/// 4 bytes are read.
127+
/// Reads a 32-bit fixed-point number from the underlying data source and returns it as a single-precision
128+
/// floating-point value.
115129
/// </summary>
116-
/// <returns>The 32-bit value read.</returns>
130+
/// <returns>A <see cref="float"/> representing the fixed-point value read from the data source.</returns>
117131
public float ReadFixed()
118132
{
119133
this.ReadInternal(this.buffer, 4);
120134
return BinaryPrimitives.ReadInt32BigEndian(this.buffer) / 65536F;
121135
}
122136

123137
/// <summary>
124-
/// Reads a 32-bit signed integer from the stream, using the bit converter
125-
/// for this reader. 4 bytes are read.
138+
/// Reads a 4-byte signed integer from the current stream.
126139
/// </summary>
127-
/// <returns>The 32-bit integer read</returns>
140+
/// <returns>The 32-bit signed integer read from the stream.</returns>
128141
public int ReadInt32()
129142
{
130143
this.ReadInternal(this.buffer, 4);
@@ -273,12 +286,14 @@ public byte ReadUInt8()
273286
/// for this reader. 3 bytes are read.
274287
/// </summary>
275288
/// <returns>The 24-bit unsigned integer read.</returns>
276-
public int ReadUInt24()
289+
public uint ReadUInt24()
277290
{
278291
byte highByte = this.ReadByte();
279-
return (highByte << 16) | this.ReadUInt16();
292+
return (uint)((highByte << 16) | this.ReadUInt16());
280293
}
281294

295+
public uint ReadOffset24() => this.ReadUInt24();
296+
282297
/// <summary>
283298
/// Reads a 32-bit unsigned integer from the stream, using the bit converter
284299
/// for this reader. 4 bytes are read.

src/SixLabors.Fonts/ClipQuad.cs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using System.Numerics;
5+
6+
namespace SixLabors.Fonts;
7+
8+
/// <summary>
9+
/// Represents a rectangular clipping region as a convex quadrilateral.
10+
/// Allows for transformation by rotation, skew, or non-uniform scaling,
11+
/// resulting in non-axis-aligned edges.
12+
/// </summary>
13+
public readonly struct ClipQuad
14+
{
15+
/// <summary>
16+
/// Initializes a new instance of the <see cref="ClipQuad"/> struct.
17+
/// </summary>
18+
/// <param name="topLeft">The top-left corner of the quadrilateral.</param>
19+
/// <param name="topRight">The top-right corner of the quadrilateral.</param>
20+
/// <param name="bottomRight">The bottom-right corner of the quadrilateral.</param>
21+
/// <param name="bottomLeft">The bottom-left corner of the quadrilateral.</param>
22+
public ClipQuad(Vector2 topLeft, Vector2 topRight, Vector2 bottomRight, Vector2 bottomLeft)
23+
{
24+
this.TopLeft = topLeft;
25+
this.TopRight = topRight;
26+
this.BottomRight = bottomRight;
27+
this.BottomLeft = bottomLeft;
28+
}
29+
30+
/// <summary>
31+
/// Gets the top-left corner of the quadrilateral.
32+
/// </summary>
33+
public Vector2 TopLeft { get; }
34+
35+
/// <summary>
36+
/// Gets the top-right corner of the quadrilateral.
37+
/// </summary>
38+
public Vector2 TopRight { get; }
39+
40+
/// <summary>
41+
/// Gets the bottom-right corner of the quadrilateral.
42+
/// </summary>
43+
public Vector2 BottomRight { get; }
44+
45+
/// <summary>
46+
/// Gets the bottom-left corner of the quadrilateral.
47+
/// </summary>
48+
public Vector2 BottomLeft { get; }
49+
50+
/// <summary>
51+
/// Creates a <see cref="ClipQuad"/> from an axis-aligned <see cref="Bounds"/> and an optional transform.
52+
/// </summary>
53+
/// <param name="bounds">The bounds representing the untransformed rectangular area.</param>
54+
/// <param name="transform">An optional transform to apply. If omitted, no transform is applied.</param>
55+
/// <returns>A <see cref="ClipQuad"/> representing the transformed rectangle.</returns>
56+
internal static ClipQuad FromBounds(in Bounds bounds, in Matrix3x2 transform)
57+
{
58+
Vector2 tl = Vector2.Transform(bounds.Min, transform);
59+
Vector2 tr = Vector2.Transform(new Vector2(bounds.Max.X, bounds.Min.Y), transform);
60+
Vector2 br = Vector2.Transform(bounds.Max, transform);
61+
Vector2 bl = Vector2.Transform(new Vector2(bounds.Min.X, bounds.Max.Y), transform);
62+
return new ClipQuad(tl, tr, br, bl);
63+
}
64+
65+
/// <summary>
66+
/// Determines whether the quadrilateral is axis-aligned within a small tolerance.
67+
/// </summary>
68+
/// <param name="tolerance">The tolerance for comparing parallel edges, typically a small epsilon.</param>
69+
/// <returns>
70+
/// <see langword="true"/> if opposite edges are parallel and of equal length; otherwise, <see langword="false"/>.
71+
/// </returns>
72+
public bool IsAxisAligned(float tolerance = 1E-4F)
73+
{
74+
Vector2 top = this.TopRight - this.TopLeft;
75+
Vector2 bottom = this.BottomRight - this.BottomLeft;
76+
Vector2 left = this.BottomLeft - this.TopLeft;
77+
Vector2 right = this.BottomRight - this.TopRight;
78+
79+
bool horizontalParallel = MathF.Abs(Vector2.Dot(Vector2.Normalize(top), Vector2.Normalize(bottom)) - 1F) < tolerance;
80+
bool verticalParallel = MathF.Abs(Vector2.Dot(Vector2.Normalize(left), Vector2.Normalize(right)) - 1F) < tolerance;
81+
82+
return horizontalParallel && verticalParallel;
83+
}
84+
}

0 commit comments

Comments
 (0)