diff --git a/src/ImageSharp.Drawing/Processing/Text/Processors/DrawTextProcessor.cs b/src/ImageSharp.Drawing/Processing/Text/Processors/DrawTextProcessor.cs index a8b5e863b2..5681405bfe 100644 --- a/src/ImageSharp.Drawing/Processing/Text/Processors/DrawTextProcessor.cs +++ b/src/ImageSharp.Drawing/Processing/Text/Processors/DrawTextProcessor.cs @@ -121,11 +121,29 @@ namespace SixLabors.ImageSharp.Processing.Text.Processors IBuffer2D buffer = operation.Map; int startY = operation.Location.Y; int startX = operation.Location.X; + int offSetSpan = 0; + if (startX < 0) + { + offSetSpan = -startX; + startX = 0; + } + + int fistRow = 0; + if (startY < 0) + { + fistRow = -startY; + } + int end = operation.Map.Height; - for (int row = 0; row < end; row++) + + int maxHeight = source.Height - startY; + end = Math.Min(end, maxHeight); + + for (int row = fistRow; row < end; row++) { int y = startY + row; - app.Apply(buffer.GetRowSpan(row), startX, y); + Span span = buffer.GetRowSpan(row).Slice(offSetSpan); + app.Apply(span, startX, y); } } } @@ -146,7 +164,7 @@ namespace SixLabors.ImageSharp.Processing.Text.Processors private Point currentRenderPosition = default(Point); private int currentRenderingGlyph = 0; - + private int offset = 0; private PointF currentPoint = default(PointF); private HashSet renderedGlyphs = new HashSet(); private Dictionary> glyphMap; @@ -161,6 +179,7 @@ namespace SixLabors.ImageSharp.Processing.Text.Processors this.Pen = pen; this.renderFill = renderFill; this.renderOutline = pen != null; + this.offset = 2; if (this.renderFill) { this.FillOperations = new List(size); @@ -169,6 +188,7 @@ namespace SixLabors.ImageSharp.Processing.Text.Processors if (this.renderOutline) { + this.offset = (int)MathF.Ceiling((pen.StrokeWidth * 2) + 2); this.OutlineOperations = new List(size); this.glyphMapPen = new Dictionary>(); } @@ -194,6 +214,9 @@ namespace SixLabors.ImageSharp.Processing.Text.Processors public bool BeginGlyph(RectangleF bounds, int cacheKey) { this.currentRenderPosition = Point.Truncate(bounds.Location); + + // we have offset our rendering origion a little bit down to prevent edge cropping, move the draw origin up to compensate + this.currentRenderPosition = new Point(this.currentRenderPosition.X - this.offset, this.currentRenderPosition.Y - this.offset); this.currentRenderingGlyph = cacheKey; if (this.renderedGlyphs.Contains(cacheKey)) { @@ -206,7 +229,7 @@ namespace SixLabors.ImageSharp.Processing.Text.Processors this.builder.Clear(); // ensure all glyphs render around [zero, zero] so offset negative root positions so when we draw the glyph we can offet it back - this.builder.SetOrigin(new PointF(-(int)bounds.X, -(int)bounds.Y)); + this.builder.SetOrigin(new PointF(-(int)bounds.X + this.offset, -(int)bounds.Y + this.offset)); this.raterizationRequired = true; return true; @@ -298,7 +321,9 @@ namespace SixLabors.ImageSharp.Processing.Text.Processors private Buffer2D Render(IPath path) { - var size = Rectangle.Ceiling(path.Bounds); + Size size = Rectangle.Ceiling(path.Bounds).Size; + size = new Size(size.Width + (this.offset * 2), size.Height + (this.offset * 2)); + float subpixelCount = 4; float offset = 0.5f; if (this.Options.Antialias) diff --git a/tests/ImageSharp.Tests/Drawing/Text/DrawTextOnImageTests.cs b/tests/ImageSharp.Tests/Drawing/Text/DrawTextOnImageTests.cs index cc7d8622a8..400917f7a8 100644 --- a/tests/ImageSharp.Tests/Drawing/Text/DrawTextOnImageTests.cs +++ b/tests/ImageSharp.Tests/Drawing/Text/DrawTextOnImageTests.cs @@ -52,6 +52,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Text TPixel color = NamedColors.Black; provider.VerifyOperation( + ImageComparer.Tolerant(imageThreshold: 0.1f, perPixelManhattanThreshold: 20), img => { img.Mutate(c => c.DrawText(text, new Font(font, fontSize), color, new PointF(x, y))); @@ -95,6 +96,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Text TPixel color = NamedColors.Black; provider.VerifyOperation( + ImageComparer.Tolerant(imageThreshold: 0.1f, perPixelManhattanThreshold: 20), img => { img.Mutate(c => c.DrawText(textOptions, sb.ToString(), font, color, new PointF(10, 5))); @@ -125,7 +127,36 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Text ImageComparer.Tolerant(imageThreshold: 0.1f, perPixelManhattanThreshold: 20), img => { - img.Mutate(c => c.DrawText(text, new Font(font, fontSize),null, Pens.Solid(color, 1), new PointF(x, y))); + img.Mutate(c => c.DrawText(text, new Font(font, fontSize), null, Pens.Solid(color, 1), new PointF(x, y))); + }, + $"pen_{fontName}-{fontSize}-{fnDisplayText}-({x},{y})", + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: true); + } + + [Theory] + [WithSolidFilledImages(200, 100, "White", PixelTypes.Rgba32, 50, 0, 0, "SixLaborsSampleAB.woff", AB)] + [WithSolidFilledImages(900, 100, "White", PixelTypes.Rgba32, 50, 0, 0, "OpenSans-Regular.ttf", TestText)] + [WithSolidFilledImages(1100, 200, "White", PixelTypes.Rgba32, 50, 150, 100, "OpenSans-Regular.ttf", TestText)] + public void FontShapesAreRenderedCorrectlyWithAPenPatterned( + TestImageProvider provider, + int fontSize, + int x, + int y, + string fontName, + string text) + where TPixel : struct, IPixel + { + Font font = CreateFont(fontName, fontSize); + string fnDisplayText = text.Replace("\n", ""); + fnDisplayText = fnDisplayText.Substring(0, Math.Min(fnDisplayText.Length, 4)); + TPixel color = NamedColors.Black; + + provider.VerifyOperation( + ImageComparer.Tolerant(imageThreshold: 0.1f, perPixelManhattanThreshold: 20), + img => + { + img.Mutate(c => c.DrawText(text, new Font(font, fontSize), null, Pens.DashDot(color, 3), new PointF(x, y))); }, $"pen_{fontName}-{fontSize}-{fnDisplayText}-({x},{y})", appendPixelTypeToFileName: false, diff --git a/tests/Images/External b/tests/Images/External index 07cdbcfca1..09059fae2d 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit 07cdbcfca121081eae97d6a9cd0e230c653eeb39 +Subproject commit 09059fae2d8bea3a4c9288ab10fb184f1b648f40