diff --git a/ImageSharp.sln b/ImageSharp.sln
index 503a5b860..1bcea0b92 100644
--- a/ImageSharp.sln
+++ b/ImageSharp.sln
@@ -68,6 +68,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageSharp.Sandbox46", "tes
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ImageSharp.Drawing.Paths", "src\ImageSharp.Drawing.Paths\ImageSharp.Drawing.Paths.xproj", "{E5BD4F96-28A8-410C-8B63-1C5731948549}"
EndProject
+Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ImageSharp.Drawing.Text", "src\ImageSharp.Drawing.Text\ImageSharp.Drawing.Text.xproj", "{329D7698-65BC-48AD-A16F-428682964493}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -122,6 +124,10 @@ Global
{E5BD4F96-28A8-410C-8B63-1C5731948549}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E5BD4F96-28A8-410C-8B63-1C5731948549}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E5BD4F96-28A8-410C-8B63-1C5731948549}.Release|Any CPU.Build.0 = Release|Any CPU
+ {329D7698-65BC-48AD-A16F-428682964493}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {329D7698-65BC-48AD-A16F-428682964493}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {329D7698-65BC-48AD-A16F-428682964493}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {329D7698-65BC-48AD-A16F-428682964493}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -140,5 +146,6 @@ Global
{9E574A07-F879-4811-9C41-5CBDC6BAFDB7} = {815C0625-CD3D-440F-9F80-2D83856AB7AE}
{96188137-5FA6-4924-AB6E-4EFF79C6E0BB} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC}
{E5BD4F96-28A8-410C-8B63-1C5731948549} = {815C0625-CD3D-440F-9F80-2D83856AB7AE}
+ {329D7698-65BC-48AD-A16F-428682964493} = {815C0625-CD3D-440F-9F80-2D83856AB7AE}
EndGlobalSection
EndGlobal
diff --git a/src/ImageSharp.Drawing.Text/DrawText.cs b/src/ImageSharp.Drawing.Text/DrawText.cs
new file mode 100644
index 000000000..28781fab2
--- /dev/null
+++ b/src/ImageSharp.Drawing.Text/DrawText.cs
@@ -0,0 +1,203 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp
+{
+ using System.Numerics;
+
+ using Drawing;
+ using Drawing.Brushes;
+ using Drawing.Pens;
+
+ using SixLabors.Fonts;
+
+ ///
+ /// Extension methods for the type.
+ ///
+ public static partial class ImageExtensions
+ {
+ ///
+ /// Draws the text onto the the image filled via the brush.
+ ///
+ /// The type of the color.
+ /// The image this method extends.
+ /// The text.
+ /// The font.
+ /// The color.
+ /// The location.
+ ///
+ /// The .
+ ///
+ public static Image DrawText(this Image source, string text, Font font, TColor color, Vector2 location)
+ where TColor : struct, IPixel
+ {
+ return source.DrawText(text, font, color, location, TextGraphicsOptions.Default);
+ }
+
+ ///
+ /// Draws the text onto the the image filled via the brush.
+ ///
+ /// The type of the color.
+ /// The image this method extends.
+ /// The text.
+ /// The font.
+ /// The color.
+ /// The location.
+ /// The options.
+ ///
+ /// The .
+ ///
+ public static Image DrawText(this Image source, string text, Font font, TColor color, Vector2 location, TextGraphicsOptions options)
+ where TColor : struct, IPixel
+ {
+ return source.DrawText(text, font, Brushes.Solid(color), null, location, options);
+ }
+
+ ///
+ /// Draws the text onto the the image filled via the brush.
+ ///
+ /// The type of the color.
+ /// The image this method extends.
+ /// The text.
+ /// The font.
+ /// The brush.
+ /// The location.
+ ///
+ /// The .
+ ///
+ public static Image DrawText(this Image source, string text, Font font, IBrush brush, Vector2 location)
+ where TColor : struct, IPixel
+ {
+ return source.DrawText(text, font, brush, location, TextGraphicsOptions.Default);
+ }
+
+ ///
+ /// Draws the text onto the the image filled via the brush.
+ ///
+ /// The type of the color.
+ /// The image this method extends.
+ /// The text.
+ /// The font.
+ /// The brush.
+ /// The location.
+ /// The options.
+ ///
+ /// The .
+ ///
+ public static Image DrawText(this Image source, string text, Font font, IBrush brush, Vector2 location, TextGraphicsOptions options)
+ where TColor : struct, IPixel
+ {
+ return source.DrawText(text, font, brush, null, location, options);
+ }
+
+ ///
+ /// Draws the text onto the the image outlined via the pen.
+ ///
+ /// The type of the color.
+ /// The image this method extends.
+ /// The text.
+ /// The font.
+ /// The pen.
+ /// The location.
+ ///
+ /// The .
+ ///
+ public static Image DrawText(this Image source, string text, Font font, IPen pen, Vector2 location)
+ where TColor : struct, IPixel
+ {
+ return source.DrawText(text, font, pen, location, TextGraphicsOptions.Default);
+ }
+
+ ///
+ /// Draws the text onto the the image outlined via the pen.
+ ///
+ /// The type of the color.
+ /// The image this method extends.
+ /// The text.
+ /// The font.
+ /// The pen.
+ /// The location.
+ /// The options.
+ ///
+ /// The .
+ ///
+ public static Image DrawText(this Image source, string text, Font font, IPen pen, Vector2 location, TextGraphicsOptions options)
+ where TColor : struct, IPixel
+ {
+ return source.DrawText(text, font, null, pen, location, options);
+ }
+
+ ///
+ /// Draws the text onto the the image filled via the brush then outlined via the pen.
+ ///
+ /// The type of the color.
+ /// The image this method extends.
+ /// The text.
+ /// The font.
+ /// The brush.
+ /// The pen.
+ /// The location.
+ ///
+ /// The .
+ ///
+ public static Image DrawText(this Image source, string text, Font font, IBrush brush, IPen pen, Vector2 location)
+ where TColor : struct, IPixel
+ {
+ return source.DrawText(text, font, brush, pen, location, TextGraphicsOptions.Default);
+ }
+
+ ///
+ /// Draws the text onto the the image filled via the brush then outlined via the pen.
+ ///
+ /// The type of the color.
+ /// The image this method extends.
+ /// The text.
+ /// The font.
+ /// The brush.
+ /// The pen.
+ /// The location.
+ /// The options.
+ ///
+ /// The .
+ ///
+ public static Image DrawText(this Image source, string text, Font font, IBrush brush, IPen pen, Vector2 location, TextGraphicsOptions options)
+ where TColor : struct, IPixel
+ {
+ GlyphBuilder glyphBuilder = new GlyphBuilder(location);
+
+ TextRenderer renderer = new TextRenderer(glyphBuilder);
+
+ Vector2 dpi = new Vector2((float)source.MetaData.HorizontalResolution, (float)source.MetaData.VerticalResolution);
+ FontSpan style = new FontSpan(font)
+ {
+ ApplyKerning = options.ApplyKerning,
+ TabWidth = options.TabWidth
+ };
+
+ renderer.RenderText(text, style, dpi);
+
+ System.Collections.Generic.IEnumerable shapesToDraw = glyphBuilder.Paths;
+
+ GraphicsOptions pathOptions = (GraphicsOptions)options;
+ if (brush != null)
+ {
+ foreach (SixLabors.Shapes.IPath s in shapesToDraw)
+ {
+ source.Fill(brush, s, pathOptions);
+ }
+ }
+
+ if (pen != null)
+ {
+ foreach (SixLabors.Shapes.IPath s in shapesToDraw)
+ {
+ source.Draw(pen, s, pathOptions);
+ }
+ }
+
+ return source;
+ }
+ }
+}
diff --git a/src/ImageSharp.Drawing.Text/GlyphBuilder.cs b/src/ImageSharp.Drawing.Text/GlyphBuilder.cs
new file mode 100644
index 000000000..ac5d01de7
--- /dev/null
+++ b/src/ImageSharp.Drawing.Text/GlyphBuilder.cs
@@ -0,0 +1,126 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Drawing
+{
+ using System.Collections.Generic;
+ using System.Numerics;
+
+ using SixLabors.Fonts;
+ using SixLabors.Shapes;
+
+ ///
+ /// rendering surface that Fonts can use to generate Shapes.
+ ///
+ internal class GlyphBuilder : IGlyphRenderer
+ {
+ private readonly PathBuilder builder = new PathBuilder();
+ private readonly List paths = new List();
+ private Vector2 currentPoint = default(Vector2);
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public GlyphBuilder()
+ : this(Vector2.Zero)
+ {
+ // glyphs are renderd realative to bottom left so invert the Y axis to allow it to render on top left origin surface
+ this.builder = new PathBuilder();
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The origin.
+ public GlyphBuilder(Vector2 origin)
+ {
+ this.builder = new PathBuilder();
+ this.builder.SetOrigin(origin);
+ }
+
+ ///
+ /// Gets the paths that have been rendered by this.
+ ///
+ public IEnumerable Paths => this.paths;
+
+ ///
+ /// Begins the glyph.
+ ///
+ void IGlyphRenderer.BeginGlyph()
+ {
+ this.builder.Clear();
+ }
+
+ ///
+ /// Begins the figure.
+ ///
+ void IGlyphRenderer.BeginFigure()
+ {
+ this.builder.StartFigure();
+ }
+
+ ///
+ /// Draws a cubic bezier from the current point to the
+ ///
+ /// The second control point.
+ /// The third control point.
+ /// The point.
+ void IGlyphRenderer.CubicBezierTo(Vector2 secondControlPoint, Vector2 thirdControlPoint, Vector2 point)
+ {
+ this.builder.AddBezier(this.currentPoint, secondControlPoint, thirdControlPoint, point);
+ this.currentPoint = point;
+ }
+
+ ///
+ /// Ends the glyph.
+ ///
+ void IGlyphRenderer.EndGlyph()
+ {
+ this.paths.Add(this.builder.Build());
+ }
+
+ ///
+ /// Ends the figure.
+ ///
+ void IGlyphRenderer.EndFigure()
+ {
+ this.builder.CloseFigure();
+ }
+
+ ///
+ /// Draws a line from the current point to the .
+ ///
+ /// The point.
+ void IGlyphRenderer.LineTo(Vector2 point)
+ {
+ this.builder.AddLine(this.currentPoint, point);
+ this.currentPoint = point;
+ }
+
+ ///
+ /// Moves to current point to the supplied vector.
+ ///
+ /// The point.
+ void IGlyphRenderer.MoveTo(Vector2 point)
+ {
+ this.builder.StartFigure();
+ this.currentPoint = point;
+ }
+
+ ///
+ /// Draws a quadratics bezier from the current point to the
+ ///
+ /// The second control point.
+ /// The point.
+ void IGlyphRenderer.QuadraticBezierTo(Vector2 secondControlPoint, Vector2 point)
+ {
+ Vector2 c1 = (((secondControlPoint - this.currentPoint) * 2) / 3) + this.currentPoint;
+ Vector2 c2 = (((secondControlPoint - point) * 2) / 3) + point;
+
+ this.builder.AddBezier(this.currentPoint, c1, c2, point);
+ this.currentPoint = point;
+ }
+ }
+}
diff --git a/src/ImageSharp.Drawing.Text/ImageSharp.Drawing.Text.xproj b/src/ImageSharp.Drawing.Text/ImageSharp.Drawing.Text.xproj
new file mode 100644
index 000000000..4dfb394cf
--- /dev/null
+++ b/src/ImageSharp.Drawing.Text/ImageSharp.Drawing.Text.xproj
@@ -0,0 +1,25 @@
+
+
+
+ 14.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+
+
+
+ 329d7698-65bc-48ad-a16f-428682964493
+ ImageSharp.Drawing
+ .\obj
+ .\bin\
+ v4.5.1
+
+
+ 2.0
+
+
+ True
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ImageSharp.Drawing.Text/Properties/AssemblyInfo.cs b/src/ImageSharp.Drawing.Text/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..fba25a9db
--- /dev/null
+++ b/src/ImageSharp.Drawing.Text/Properties/AssemblyInfo.cs
@@ -0,0 +1,6 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+// Common values read from `AssemblyInfo.Common.cs`
diff --git a/src/ImageSharp.Drawing.Text/TextGraphicsOptions.cs b/src/ImageSharp.Drawing.Text/TextGraphicsOptions.cs
new file mode 100644
index 000000000..e707ef5e5
--- /dev/null
+++ b/src/ImageSharp.Drawing.Text/TextGraphicsOptions.cs
@@ -0,0 +1,68 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Drawing
+{
+ ///
+ /// Options for influencing the drawing functions.
+ ///
+ public struct TextGraphicsOptions
+ {
+ ///
+ /// Represents the default .
+ ///
+ public static readonly TextGraphicsOptions Default = new TextGraphicsOptions(true);
+
+ ///
+ /// Whether antialiasing should be applied.
+ ///
+ public bool Antialias;
+
+ ///
+ /// Whether the text should be drawing with kerning enabled.
+ ///
+ public bool ApplyKerning;
+
+ ///
+ /// The number of space widths a tab should lock to.
+ ///
+ public float TabWidth;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// If set to true [enable antialiasing].
+ public TextGraphicsOptions(bool enableAntialiasing)
+ {
+ this.Antialias = enableAntialiasing;
+ this.ApplyKerning = true;
+ this.TabWidth = 4;
+ }
+
+ ///
+ /// Performs an implicit conversion from to .
+ ///
+ /// The options.
+ ///
+ /// The result of the conversion.
+ ///
+ public static implicit operator TextGraphicsOptions(GraphicsOptions options)
+ {
+ return new TextGraphicsOptions(options.Antialias);
+ }
+
+ ///
+ /// Performs an explicit conversion from to .
+ ///
+ /// The options.
+ ///
+ /// The result of the conversion.
+ ///
+ public static explicit operator GraphicsOptions(TextGraphicsOptions options)
+ {
+ return new GraphicsOptions(options.Antialias);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp.Drawing.Text/project.json b/src/ImageSharp.Drawing.Text/project.json
new file mode 100644
index 000000000..66d0e7d26
--- /dev/null
+++ b/src/ImageSharp.Drawing.Text/project.json
@@ -0,0 +1,95 @@
+{
+ "version": "1.0.0-alpha2-*",
+ "title": "ImageSharp.Drawing.Text",
+ "description": "A cross-platform library for the processing of image files; written in C#",
+ "authors": [
+ "James Jackson-South and contributors"
+ ],
+ "packOptions": {
+ "owners": [
+ "James Jackson-South and contributors"
+ ],
+ "projectUrl": "https://github.com/JimBobSquarePants/ImageSharp",
+ "licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0",
+ "iconUrl": "https://raw.githubusercontent.com/JimBobSquarePants/ImageSharp/master/build/icons/imagesharp-logo-128.png",
+ "requireLicenseAcceptance": false,
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/JimBobSquarePants/ImageSharp"
+ },
+ "tags": [
+ "Image Resize Crop Gif Jpg Jpeg Bitmap Png Core"
+ ]
+ },
+ "buildOptions": {
+ "allowUnsafe": true,
+ "xmlDoc": true,
+ "additionalArguments": [ "/additionalfile:../Shared/stylecop.json", "/ruleset:../../ImageSharp.ruleset" ],
+ "compile": [
+ "../Shared/*.cs"
+ ]
+ },
+ "configurations": {
+ "Release": {
+ "buildOptions": {
+ "warningsAsErrors": true,
+ "optimize": true
+ }
+ }
+ },
+ "dependencies": {
+ "ImageSharp": {
+ "target": "project"
+ },
+ "SixLabors.Fonts": "0.1.0-alpha0001",
+ "ImageSharp.Drawing.Paths": {
+ "target": "project"
+ },
+ "StyleCop.Analyzers": {
+ "version": "1.0.0",
+ "type": "build"
+ },
+ "System.Buffers": "4.0.0",
+ "System.Runtime.CompilerServices.Unsafe": "4.0.0"
+ },
+ "frameworks": {
+ "netstandard1.1": {
+ "dependencies": {
+ "System.Collections": "4.0.11",
+ "System.Diagnostics.Debug": "4.0.11",
+ "System.Diagnostics.Tools": "4.0.1",
+ "System.IO": "4.1.0",
+ "System.IO.Compression": "4.1.0",
+ "System.Linq": "4.1.0",
+ "System.Numerics.Vectors": "4.1.1",
+ "System.ObjectModel": "4.0.12",
+ "System.Resources.ResourceManager": "4.0.1",
+ "System.Runtime.Extensions": "4.1.0",
+ "System.Runtime.InteropServices": "4.1.0",
+ "System.Runtime.Numerics": "4.0.1",
+ "System.Text.Encoding.Extensions": "4.0.11",
+ "System.Threading": "4.0.11",
+ "System.Threading.Tasks": "4.0.11",
+ "System.Threading.Tasks.Parallel": "4.0.1"
+ }
+ },
+ "net45": {
+ "dependencies": {
+ "System.Numerics.Vectors": "4.1.1",
+ "System.Threading.Tasks.Parallel": "4.0.0"
+ },
+ "frameworkAssemblies": {
+ "System.Runtime": { "type": "build" }
+ }
+ },
+ "net461": {
+ "dependencies": {
+ "System.Threading.Tasks.Parallel": "4.0.0"
+ },
+ "frameworkAssemblies": {
+ "System.Runtime": { "type": "build" },
+ "System.Numerics": "4.0.0.0"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Drawing/Text/DrawText.cs b/tests/ImageSharp.Tests/Drawing/Text/DrawText.cs
new file mode 100644
index 000000000..2a2cb8a07
--- /dev/null
+++ b/tests/ImageSharp.Tests/Drawing/Text/DrawText.cs
@@ -0,0 +1,194 @@
+
+namespace ImageSharp.Tests.Drawing.Text
+{
+ using System;
+ using System.IO;
+ using ImageSharp;
+ using ImageSharp.Drawing.Brushes;
+ using Processing;
+ using System.Collections.Generic;
+ using Xunit;
+ using ImageSharp.Drawing;
+ using System.Numerics;
+ using SixLabors.Shapes;
+ using ImageSharp.Drawing.Processors;
+ using ImageSharp.Drawing.Pens;
+ using SixLabors.Fonts;
+ using Paths;
+
+ public class DrawText : IDisposable
+ {
+ Color color = Color.HotPink;
+ SolidBrush brush = Brushes.Solid(Color.HotPink);
+ IPath path = new SixLabors.Shapes.Path(new LinearLineSegment(new Vector2[] {
+ new Vector2(10,10),
+ new Vector2(20,10),
+ new Vector2(20,10),
+ new Vector2(30,10),
+ }));
+ private ProcessorWatchingImage img;
+ private readonly FontCollection FontCollection;
+ private readonly Font Font;
+
+ public DrawText()
+ {
+ this.FontCollection = new FontCollection();
+ this.Font = FontCollection.Install(TestFontUtilities.GetPath("SixLaborsSampleAB.woff"));
+ this.img = new ProcessorWatchingImage(10, 10);
+ }
+
+ public void Dispose()
+ {
+ img.Dispose();
+ }
+
+ [Fact]
+ public void FillsForEachACharachterWhenBrushSetAndNotPen()
+ {
+ img.DrawText("123", this.Font, Brushes.Solid(Color.Red), null, Vector2.Zero, new TextGraphicsOptions(true));
+
+ Assert.NotEmpty(img.ProcessorApplications);
+ Assert.Equal(3, img.ProcessorApplications.Count); // 3 fills where applied
+ Assert.IsType>(img.ProcessorApplications[0].processor);
+ }
+
+ [Fact]
+ public void FillsForEachACharachterWhenBrushSetAndNotPenDefaultOptions()
+ {
+ img.DrawText("123", this.Font, Brushes.Solid(Color.Red), null, Vector2.Zero);
+
+ Assert.NotEmpty(img.ProcessorApplications);
+ Assert.Equal(3, img.ProcessorApplications.Count); // 3 fills where applied
+ Assert.IsType>(img.ProcessorApplications[0].processor);
+ }
+
+
+ [Fact]
+ public void FillsForEachACharachterWhenBrushSet()
+ {
+ img.DrawText("123", this.Font, Brushes.Solid(Color.Red), Vector2.Zero, new TextGraphicsOptions(true));
+
+ Assert.NotEmpty(img.ProcessorApplications);
+ Assert.Equal(3, img.ProcessorApplications.Count); // 3 fills where applied
+ Assert.IsType>(img.ProcessorApplications[0].processor);
+ }
+
+ [Fact]
+ public void FillsForEachACharachterWhenBrushSetDefaultOptions()
+ {
+ img.DrawText("123", this.Font, Brushes.Solid(Color.Red), Vector2.Zero);
+
+ Assert.NotEmpty(img.ProcessorApplications);
+ Assert.Equal(3, img.ProcessorApplications.Count); // 3 fills where applied
+ Assert.IsType>(img.ProcessorApplications[0].processor);
+ }
+
+ [Fact]
+ public void FillsForEachACharachterWhenColorSet()
+ {
+ img.DrawText("123", this.Font, Color.Red, Vector2.Zero, new TextGraphicsOptions(true));
+
+ Assert.NotEmpty(img.ProcessorApplications);
+ Assert.Equal(3, img.ProcessorApplications.Count);
+ FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor);
+
+ SolidBrush brush = Assert.IsType>(processor.Brush);
+ Assert.Equal(Color.Red, brush.Color);
+ }
+
+ [Fact]
+ public void FillsForEachACharachterWhenColorSetDefaultOptions()
+ {
+ img.DrawText("123", this.Font, Color.Red, Vector2.Zero);
+
+ Assert.NotEmpty(img.ProcessorApplications);
+ Assert.Equal(3, img.ProcessorApplications.Count);
+ Assert.IsType>(img.ProcessorApplications[0].processor);
+ FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor);
+
+ SolidBrush brush = Assert.IsType>(processor.Brush);
+ Assert.Equal(Color.Red, brush.Color);
+ }
+
+ [Fact]
+ public void DrawForEachACharachterWhenPenSetAndNotBrush()
+ {
+ img.DrawText("123", this.Font, null, Pens.Dash(Color.Red, 1), Vector2.Zero, new TextGraphicsOptions(true));
+
+ Assert.NotEmpty(img.ProcessorApplications);
+ Assert.Equal(3, img.ProcessorApplications.Count); // 3 fills where applied
+ Assert.IsType>(img.ProcessorApplications[0].processor);
+ }
+
+ [Fact]
+ public void DrawForEachACharachterWhenPenSetAndNotBrushDefaultOptions()
+ {
+ img.DrawText("123", this.Font, null, Pens.Dash(Color.Red, 1), Vector2.Zero);
+
+ Assert.NotEmpty(img.ProcessorApplications);
+ Assert.Equal(3, img.ProcessorApplications.Count); // 3 fills where applied
+ Assert.IsType>(img.ProcessorApplications[0].processor);
+ }
+
+
+ [Fact]
+ public void DrawForEachACharachterWhenPenSet()
+ {
+ img.DrawText("123", this.Font, Pens.Dash(Color.Red, 1), Vector2.Zero, new TextGraphicsOptions(true));
+
+ Assert.NotEmpty(img.ProcessorApplications);
+ Assert.Equal(3, img.ProcessorApplications.Count); // 3 fills where applied
+ Assert.IsType>(img.ProcessorApplications[0].processor);
+ }
+
+ [Fact]
+ public void DrawForEachACharachterWhenPenSetDefaultOptions()
+ {
+ img.DrawText("123", this.Font, Pens.Dash(Color.Red, 1), Vector2.Zero);
+
+ Assert.NotEmpty(img.ProcessorApplications);
+ Assert.Equal(3, img.ProcessorApplications.Count); // 3 fills where applied
+ Assert.IsType>(img.ProcessorApplications[0].processor);
+ }
+
+ [Fact]
+ public void DrawForEachACharachterWhenPenSetAndFillFroEachWhenBrushSet()
+ {
+ img.DrawText("123", this.Font, Brushes.Solid(Color.Red), Pens.Dash(Color.Red, 1), Vector2.Zero, new TextGraphicsOptions(true));
+
+ Assert.NotEmpty(img.ProcessorApplications);
+ Assert.Equal(6, img.ProcessorApplications.Count);
+ }
+
+ [Fact]
+ public void DrawForEachACharachterWhenPenSetAndFillFroEachWhenBrushSetDefaultOptions()
+ {
+ img.DrawText("123", this.Font, Brushes.Solid(Color.Red), Pens.Dash(Color.Red, 1), Vector2.Zero);
+
+ Assert.NotEmpty(img.ProcessorApplications);
+ Assert.Equal(6, img.ProcessorApplications.Count);
+ }
+
+ [Fact]
+ public void BrushAppliesBeforPen()
+ {
+ img.DrawText("1", this.Font, Brushes.Solid(Color.Red), Pens.Dash(Color.Red, 1), Vector2.Zero, new TextGraphicsOptions(true));
+
+ Assert.NotEmpty(img.ProcessorApplications);
+ Assert.Equal(2, img.ProcessorApplications.Count);
+ Assert.IsType>(img.ProcessorApplications[0].processor);
+ Assert.IsType>(img.ProcessorApplications[1].processor);
+ }
+
+ [Fact]
+ public void BrushAppliesBeforPenDefaultOptions()
+ {
+ img.DrawText("1", this.Font, Brushes.Solid(Color.Red), Pens.Dash(Color.Red, 1), Vector2.Zero);
+
+ Assert.NotEmpty(img.ProcessorApplications);
+ Assert.Equal(2, img.ProcessorApplications.Count);
+ Assert.IsType>(img.ProcessorApplications[0].processor);
+ Assert.IsType>(img.ProcessorApplications[1].processor);
+ }
+ }
+}
diff --git a/tests/ImageSharp.Tests/Drawing/Text/GlyphBuilder.cs b/tests/ImageSharp.Tests/Drawing/Text/GlyphBuilder.cs
new file mode 100644
index 000000000..1faa5edd3
--- /dev/null
+++ b/tests/ImageSharp.Tests/Drawing/Text/GlyphBuilder.cs
@@ -0,0 +1,68 @@
+
+namespace ImageSharp.Tests.Drawing.Text
+{
+ using ImageSharp.Drawing;
+ using SixLabors.Fonts;
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Numerics;
+ using System.Threading.Tasks;
+ using Xunit;
+
+ public class GlyphBuilderTests
+ {
+ [Fact]
+ public void OriginUsed()
+ {
+ // Y axis is inverted as it expects to be drawing for bottom left
+ var fullBuilder = new GlyphBuilder(new System.Numerics.Vector2(10, 99));
+ IGlyphRenderer builder = fullBuilder;
+
+ builder.BeginGlyph();
+ builder.BeginFigure();
+ builder.MoveTo(new Vector2(0, 0));
+ builder.LineTo(new Vector2(0, 10)); // becomes 0, -10
+
+ builder.CubicBezierTo(
+ new Vector2(15, 15), // control point - will not be in the final point collection
+ new Vector2(15, 10), // control point - will not be in the final point collection
+ new Vector2(10, 10));// becomes 10, -10
+
+ builder.QuadraticBezierTo(
+ new Vector2(10, 5), // control point - will not be in the final point collection
+ new Vector2(10, 0));
+
+ builder.EndFigure();
+ builder.EndGlyph();
+
+ var points = fullBuilder.Paths.Single().Flatten().Single().Points;
+
+ Assert.Contains(new Vector2(10, 99), points);
+ Assert.Contains(new Vector2(10, 109), points);
+ Assert.Contains(new Vector2(20, 99), points);
+ Assert.Contains(new Vector2(20, 109), points);
+ }
+
+ [Fact]
+ public void EachGlypeCausesNewPath()
+ {
+ // Y axis is inverted as it expects to be drawing for bottom left
+ GlyphBuilder fullBuilder = new GlyphBuilder();
+ IGlyphRenderer builder = fullBuilder;
+ for (var i = 0; i < 10; i++)
+ {
+ builder.BeginGlyph();
+ builder.BeginFigure();
+ builder.MoveTo(new Vector2(0, 0));
+ builder.LineTo(new Vector2(0, 10)); // becomes 0, -10
+ builder.LineTo(new Vector2(10, 10));// becomes 10, -10
+ builder.LineTo(new Vector2(10, 0));
+ builder.EndFigure();
+ builder.EndGlyph();
+ }
+
+ Assert.Equal(10, fullBuilder.Paths.Count());
+ }
+ }
+}
diff --git a/tests/ImageSharp.Tests/Drawing/Text/OutputText.cs b/tests/ImageSharp.Tests/Drawing/Text/OutputText.cs
new file mode 100644
index 000000000..ae007727a
--- /dev/null
+++ b/tests/ImageSharp.Tests/Drawing/Text/OutputText.cs
@@ -0,0 +1,41 @@
+
+namespace ImageSharp.Tests.Drawing.Text
+{
+ using System;
+ using System.IO;
+ using ImageSharp;
+ using ImageSharp.Drawing.Brushes;
+ using Processing;
+ using System.Collections.Generic;
+ using Xunit;
+ using ImageSharp.Drawing;
+ using System.Numerics;
+ using SixLabors.Shapes;
+ using ImageSharp.Drawing.Processors;
+ using ImageSharp.Drawing.Pens;
+ using SixLabors.Fonts;
+
+ public class OutputText : FileTestBase
+ {
+ private readonly FontCollection FontCollection;
+ private readonly Font Font;
+
+ public OutputText()
+ {
+ this.FontCollection = new FontCollection();
+ this.Font = FontCollection.Install(TestFontUtilities.GetPath("SixLaborsSampleAB.woff"));
+ }
+
+ [Fact]
+ public void DrawAB()
+ {
+ //draws 2 overlapping triangle glyphs twice 1 set on each line
+ using (var img = new Image(100, 200))
+ {
+ img.Fill(Color.DarkBlue)
+ .DrawText("AB\nAB", new Font(this.Font, 50), Color.Red, new Vector2(0, 0));
+ img.Save($"{this.CreateOutputDirectory("Drawing", "Text")}/AB.png");
+ }
+ }
+ }
+}
diff --git a/tests/ImageSharp.Tests/TestFont.cs b/tests/ImageSharp.Tests/TestFont.cs
new file mode 100644
index 000000000..3a5bb2b2c
--- /dev/null
+++ b/tests/ImageSharp.Tests/TestFont.cs
@@ -0,0 +1,90 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Tests
+{
+ using System;
+ using System.Collections.Concurrent;
+ using System.Collections.Generic;
+ using System.IO;
+ using System.Linq;
+ using System.Reflection;
+
+ ///
+ /// A test image file.
+ ///
+ public static class TestFontUtilities
+ {
+ ///
+ /// The formats directory.
+ ///
+ private static readonly string FormatsDirectory = GetFontsDirectory();
+
+ ///
+ /// Gets the full qualified path to the file.
+ ///
+ ///
+ /// The file path.
+ ///
+ ///
+ /// The .
+ ///
+ public static string GetPath(string file)
+ {
+ return Path.Combine(FormatsDirectory, file);
+ }
+
+ ///
+ /// Gets the correct path to the formats directory.
+ ///
+ ///
+ /// The .
+ ///
+ private static string GetFontsDirectory()
+ {
+ List directories = new List< string > {
+ "TestFonts/", // Here for code coverage tests.
+ "tests/ImageSharp.Tests/TestFonts/", // from travis/build script
+ "../../../ImageSharp.Tests/TestFonts/", // from Sandbox46
+ "../../../../TestFonts/"
+ };
+
+ directories = directories.SelectMany(x => new[]
+ {
+ Path.GetFullPath(x)
+ }).ToList();
+
+ AddFormatsDirectoryFromTestAssebmlyPath(directories);
+
+ var directory = directories.FirstOrDefault(x => Directory.Exists(x));
+
+ if(directory != null)
+ {
+ return directory;
+ }
+
+ throw new System.Exception($"Unable to find Fonts directory at any of these locations [{string.Join(", ", directories)}]");
+ }
+
+ ///
+ /// The path returned by Path.GetFullPath(x) can be relative to dotnet framework directory
+ /// in certain scenarios like dotTrace test profiling.
+ /// This method calculates and adds the format directory based on the ImageSharp.Tests assembly location.
+ ///
+ /// The directories list
+ private static void AddFormatsDirectoryFromTestAssebmlyPath(List directories)
+ {
+ string assemblyLocation = typeof(TestFile).GetTypeInfo().Assembly.Location;
+ assemblyLocation = Path.GetDirectoryName(assemblyLocation);
+
+ if (assemblyLocation != null)
+ {
+ string dirFromAssemblyLocation = Path.Combine(assemblyLocation, "../../../TestFonts/");
+ dirFromAssemblyLocation = Path.GetFullPath(dirFromAssemblyLocation);
+ directories.Add(dirFromAssemblyLocation);
+ }
+ }
+ }
+}
diff --git a/tests/ImageSharp.Tests/TestFonts/SixLaborsSampleAB.woff b/tests/ImageSharp.Tests/TestFonts/SixLaborsSampleAB.woff
new file mode 100644
index 000000000..277749dfb
Binary files /dev/null and b/tests/ImageSharp.Tests/TestFonts/SixLaborsSampleAB.woff differ
diff --git a/tests/ImageSharp.Tests/project.json b/tests/ImageSharp.Tests/project.json
index 3761bb385..7c67a5c70 100644
--- a/tests/ImageSharp.Tests/project.json
+++ b/tests/ImageSharp.Tests/project.json
@@ -31,6 +31,9 @@
"ImageSharp.Drawing.Paths": {
"target": "project"
},
+ "ImageSharp.Drawing.Text": {
+ "target": "project"
+ },
"ImageSharp.Formats.Png": {
"target": "project"
},