Skip to content

Commit fa73c5c

Browse files
Handle potential degenerate paths from decoration adjustments
1 parent b52f8f8 commit fa73c5c

File tree

5 files changed

+73
-60
lines changed

5 files changed

+73
-60
lines changed

src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor{TPixel}.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Licensed under the Six Labors Split License.
33

44
using System.Numerics;
5-
using SixLabors.Fonts;
5+
using SixLabors.Fonts.Rendering;
66
using SixLabors.ImageSharp.Memory;
77
using SixLabors.ImageSharp.Processing.Processors;
88

src/ImageSharp.Drawing/Processing/Processors/Text/RichTextGlyphRenderer.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -269,10 +269,11 @@ public override void SetDecoration(TextDecorations textDecorations, Vector2 star
269269
return;
270270
}
271271

272+
Brush? brush = null;
272273
Pen? pen = null;
273274
if (this.currentTextRun is RichTextRun drawingRun)
274275
{
275-
this.currentBrush = drawingRun.Brush;
276+
brush = drawingRun.Brush;
276277

277278
if (textDecorations == TextDecorations.Strikeout)
278279
{
@@ -297,9 +298,8 @@ public override void SetDecoration(TextDecorations textDecorations, Vector2 star
297298
}
298299
else
299300
{
300-
// Brush cannot be null if pen is null.
301301
// The thickness of the line has already been clamped in the base class.
302-
pen = new SolidPen((this.currentBrush ?? this.defaultBrush)!, thickness);
302+
pen = new SolidPen((brush ?? this.defaultBrush)!, thickness);
303303
}
304304

305305
// Path has already been added to the collection via the base class.
@@ -337,7 +337,7 @@ public override void SetDecoration(TextDecorations textDecorations, Vector2 star
337337
}
338338

339339
// Scale about the chosen anchor so the fixed edge stays in place.
340-
outline = path.Transform(Matrix3x2.CreateScale(scale, anchor));
340+
outline = outline.Transform(Matrix3x2.CreateScale(scale, anchor));
341341
}
342342
}
343343

@@ -536,7 +536,7 @@ private Buffer2D<float> Render(IPath path)
536536
0,
537537
size.Height,
538538
subpixelCount,
539-
IntersectionRule.NonZero,
539+
IntersectionRule.NonZero, // TODO: Should be from layer.
540540
this.memoryAllocator);
541541

542542
try

src/ImageSharp.Drawing/Shapes/Text/BaseGlyphBuilder.cs

Lines changed: 65 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -130,23 +130,21 @@ void IGlyphRenderer.EndGlyph()
130130
if (!this.usedLayers)
131131
{
132132
IPath path = this.Builder.Build();
133-
if (!path.Bounds.IsEmpty)
133+
134+
this.CurrentPaths.Add(path);
135+
136+
if (this.graphemeBuilder is not null)
134137
{
135-
this.CurrentPaths.Add(path);
138+
this.graphemeBuilder.AddPath(path);
139+
this.graphemeBuilder.AddLayer(
140+
startIndex: this.graphemePathCount,
141+
count: 1,
142+
paint: null,
143+
fillRule: FillRule.NonZero,
144+
bounds: path.Bounds,
145+
kind: GlyphLayerKind.Glyph);
136146

137-
if (this.graphemeBuilder is not null)
138-
{
139-
this.graphemeBuilder.AddPath(path);
140-
this.graphemeBuilder.AddLayer(
141-
startIndex: this.graphemePathCount,
142-
count: 1,
143-
paint: null,
144-
fillRule: FillRule.NonZero,
145-
bounds: path.Bounds,
146-
kind: GlyphLayerKind.Glyph);
147-
148-
this.graphemePathCount++;
149-
}
147+
this.graphemePathCount++;
150148
}
151149
}
152150

@@ -212,23 +210,20 @@ void IGlyphRenderer.EndLayer()
212210
}
213211

214212
IPath path = this.Builder.Build();
215-
if (!path.Bounds.IsEmpty)
216-
{
217-
this.CurrentPaths.Add(path);
213+
this.CurrentPaths.Add(path);
218214

219-
if (this.graphemeBuilder is not null)
220-
{
221-
this.graphemeBuilder.AddPath(path);
222-
this.graphemeBuilder.AddLayer(
223-
startIndex: this.layerStartIndex,
224-
count: 1,
225-
paint: this.activeLayerPaint,
226-
fillRule: this.activeLayerFillRule,
227-
bounds: path.Bounds,
228-
kind: GlyphLayerKind.Painted);
229-
230-
this.graphemePathCount++;
231-
}
215+
if (this.graphemeBuilder is not null)
216+
{
217+
this.graphemeBuilder.AddPath(path);
218+
this.graphemeBuilder.AddLayer(
219+
startIndex: this.layerStartIndex,
220+
count: 1,
221+
paint: this.activeLayerPaint,
222+
fillRule: this.activeLayerFillRule,
223+
bounds: path.Bounds,
224+
kind: GlyphLayerKind.Painted);
225+
226+
this.graphemePathCount++;
232227
}
233228

234229
this.Builder.Clear();
@@ -266,21 +261,32 @@ void IGlyphRenderer.SetDecoration(TextDecorations textDecorations, Vector2 start
266261

267262
if (previous != null)
268263
{
264+
float prevThickness = previous.Value.Thickness;
265+
Vector2 prevStart = previous.Value.Start;
266+
Vector2 prevEnd = previous.Value.End;
267+
268+
// If the previous line is identical to the new one ignore it.
269+
// This can happen when multiple glyph layers are used.
270+
if (prevStart == start && prevEnd == end)
271+
{
272+
return;
273+
}
274+
269275
// Align the new line with the previous one if they are close enough.
270276
if (rotated)
271277
{
272-
if (thickness == previous.Value.Thickness
273-
&& previous.Value.End.Y + 1 >= start.Y
274-
&& previous.Value.End.X == start.X)
278+
if (thickness == prevThickness
279+
&& prevEnd.Y + 1 >= start.Y
280+
&& prevEnd.X == start.X)
275281
{
276-
start = previous.Value.End;
282+
start = prevEnd;
277283
}
278284
}
279-
else if (thickness == previous.Value.Thickness
280-
&& previous.Value.End.Y == start.Y
281-
&& previous.Value.End.X + 1 >= start.X)
285+
else if (thickness == prevThickness
286+
&& prevEnd.Y == start.Y
287+
&& prevEnd.X + 1 >= start.X)
282288
{
283-
start = previous.Value.End;
289+
start = prevEnd;
284290
}
285291
}
286292

@@ -332,23 +338,28 @@ void IGlyphRenderer.SetDecoration(TextDecorations textDecorations, Vector2 start
332338
renderer.EndFigure();
333339

334340
IPath path = this.Builder.Build();
335-
if (!path.Bounds.IsEmpty)
336-
{
337-
this.CurrentPaths.Add(path);
338341

339-
if (this.graphemeBuilder is not null)
340-
{
341-
this.graphemeBuilder.AddPath(path);
342-
this.graphemeBuilder.AddLayer(
343-
startIndex: this.layerStartIndex,
344-
count: 1,
345-
paint: this.activeLayerPaint,
346-
fillRule: FillRule.NonZero,
347-
bounds: path.Bounds,
348-
kind: GlyphLayerKind.Decoration);
342+
// If the path is degenerate (e.g. zero width line) we just skip it
343+
// and return. This might happen when clamping moves the points.
344+
if (path.Bounds.IsEmpty)
345+
{
346+
this.Builder.Clear();
347+
return;
348+
}
349349

350-
this.graphemePathCount++;
351-
}
350+
this.CurrentPaths.Add(path);
351+
if (this.graphemeBuilder is not null)
352+
{
353+
this.graphemeBuilder.AddPath(path);
354+
this.graphemeBuilder.AddLayer(
355+
startIndex: this.layerStartIndex,
356+
count: 1,
357+
paint: this.activeLayerPaint,
358+
fillRule: FillRule.NonZero,
359+
bounds: path.Bounds,
360+
kind: GlyphLayerKind.Decoration);
361+
362+
this.graphemePathCount++;
352363
}
353364

354365
this.Builder.Clear();

src/ImageSharp.Drawing/Shapes/Text/PathGlyphBuilder.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Numerics;
55
using System.Runtime.CompilerServices;
66
using SixLabors.Fonts;
7+
using SixLabors.Fonts.Rendering;
78

89
namespace SixLabors.ImageSharp.Drawing.Text;
910

src/ImageSharp.Drawing/Shapes/Text/TextBuilder.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.Drawing.Shapes.Text;
78
using SixLabors.ImageSharp.Drawing.Text;
89

0 commit comments

Comments
 (0)