@@ -118,13 +118,33 @@ public double Width
118118 /// <param name="linePoints">The input points to stroke.</param>
119119 /// <param name="isClosed">Whether the input is a closed ring.</param>
120120 /// <returns>The stroked outline as a closed point array.</returns>
121+ /// <remarks>
122+ /// When a 2-point input contains identical points (degenerate case), this method generates
123+ /// a cap shape at that point: a circle for round caps or a square for square/butt caps.
124+ /// This ensures that even degenerate input produces visible output when stroked.
125+ /// </remarks>
121126 public PointF [ ] ProcessPath ( ReadOnlySpan < PointF > linePoints , bool isClosed )
122127 {
123128 if ( linePoints . Length < 2 )
124129 {
125130 return [ ] ;
126131 }
127132
133+ // Special case: for 2-point inputs, check if both points are identical (degenerate case)
134+ // This avoids overhead for longer paths where the filtering logic handles near-duplicates
135+ if ( linePoints . Length == 2 )
136+ {
137+ PointF p0 = linePoints [ 0 ] ;
138+ PointF p1 = linePoints [ 1 ] ;
139+
140+ if ( Math . Abs ( p1 . X - p0 . X ) <= Constants . Misc . VertexDistanceEpsilon &&
141+ Math . Abs ( p1 . Y - p0 . Y ) <= Constants . Misc . VertexDistanceEpsilon )
142+ {
143+ // Both points are identical - generate a point cap shape
144+ return this . GeneratePointCap ( p0 . X , p0 . Y ) ;
145+ }
146+ }
147+
128148 this . Reset ( ) ;
129149 this . AddLinePath ( linePoints ) ;
130150
@@ -762,6 +782,52 @@ private void CalcJoin(ref VertexDistance v0, ref VertexDistance v1, ref VertexDi
762782 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
763783 private void AddPoint ( double x , double y ) => this . outVertices . Add ( new PointF ( ( float ) x , ( float ) y ) ) ;
764784
785+ /// <summary>
786+ /// Generates a cap shape for a degenerate point (when all input points are identical).
787+ /// Creates a circle for round caps or a square for square/butt caps.
788+ /// </summary>
789+ /// <param name="x">The X coordinate of the point.</param>
790+ /// <param name="y">The Y coordinate of the point.</param>
791+ /// <returns>The vertices forming the cap shape.</returns>
792+ private PointF [ ] GeneratePointCap ( double x , double y )
793+ {
794+ if ( this . LineCap == LineCap . Round )
795+ {
796+ // Generate a circle with radius = strokeWidth
797+ double da = Math . Acos ( this . widthAbs / ( this . widthAbs + ( 0.125 / this . ApproximationScale ) ) ) * 2 ;
798+ int n = Math . Max ( 4 , ( int ) ( Constants . Misc . PiMul2 / da ) ) ;
799+ double angleStep = Constants . Misc . PiMul2 / n ;
800+
801+ PointF [ ] points = new PointF [ n + 1 ] ;
802+
803+ for ( int i = 0 ; i < n ; i ++ )
804+ {
805+ double angle = i * angleStep ;
806+ points [ i ] = new PointF (
807+ ( float ) ( x + ( Math . Cos ( angle ) * this . strokeWidth ) ) ,
808+ ( float ) ( y + ( Math . Sin ( angle ) * this . strokeWidth ) ) ) ;
809+ }
810+
811+ // Close the circle
812+ points [ n ] = points [ 0 ] ;
813+
814+ return points ;
815+ }
816+ else
817+ {
818+ // Generate a square cap (used for both Square and Butt caps)
819+ double w = this . strokeWidth ;
820+ return
821+ [
822+ new PointF ( ( float ) ( x - w ) , ( float ) ( y - w ) ) ,
823+ new PointF ( ( float ) ( x + w ) , ( float ) ( y - w ) ) ,
824+ new PointF ( ( float ) ( x + w ) , ( float ) ( y + w ) ) ,
825+ new PointF ( ( float ) ( x - w ) , ( float ) ( y + w ) ) ,
826+ new PointF ( ( float ) ( x - w ) , ( float ) ( y - w ) ) // Close the square
827+ ] ;
828+ }
829+ }
830+
765831 private enum Status
766832 {
767833 Initial ,
0 commit comments