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" },