@@ -225,16 +225,47 @@ private static PointData[] Simplify(IReadOnlyList<ILineSegment> segments, bool i
225225 {
226226 List < PointF > simplified = new ( segments . Count ) ;
227227
228+ // Track indices where collinear direction reversals represent user-intended
229+ // geometry: interior points of multi-point linear segments, and junction
230+ // points between two linear segments (e.g. PathBuilder LineTo → LineTo).
231+ // Reversals at all other indices (flattened curves, curve junctions) are
232+ // artifacts and should be removed normally.
233+ HashSet < int > ? linearReversalIndices = null ;
234+ ILineSegment ? prevSeg = null ;
235+
228236 foreach ( ILineSegment seg in segments )
229237 {
238+ int start = simplified . Count ;
230239 ReadOnlyMemory < PointF > points = seg . Flatten ( ) ;
231240 simplified . AddRange ( points . Span ) ;
241+
242+ if ( seg is LinearLineSegment )
243+ {
244+ // Interior points of a multi-point linear segment (e.g. DrawLine with 3+ points).
245+ if ( points . Length > 2 )
246+ {
247+ linearReversalIndices ??= [ ] ;
248+ for ( int i = start + 1 ; i < start + points . Length - 1 ; i ++ )
249+ {
250+ _ = linearReversalIndices . Add ( i ) ;
251+ }
252+ }
253+
254+ // Junction between two linear segments (e.g. PathBuilder LineTo → LineTo).
255+ if ( prevSeg is LinearLineSegment && start > 0 )
256+ {
257+ linearReversalIndices ??= [ ] ;
258+ _ = linearReversalIndices . Add ( start ) ;
259+ }
260+ }
261+
262+ prevSeg = seg ;
232263 }
233264
234- return Simplify ( CollectionsMarshal . AsSpan ( simplified ) , isClosed , removeCloseAndCollinear ) ;
265+ return Simplify ( CollectionsMarshal . AsSpan ( simplified ) , isClosed , removeCloseAndCollinear , linearReversalIndices ) ;
235266 }
236267
237- private static PointData [ ] Simplify ( ReadOnlySpan < PointF > points , bool isClosed , bool removeCloseAndCollinear )
268+ private static PointData [ ] Simplify ( ReadOnlySpan < PointF > points , bool isClosed , bool removeCloseAndCollinear , HashSet < int > ? linearReversalIndices = null )
238269 {
239270 int polyCorners = points . Length ;
240271 if ( polyCorners == 0 )
@@ -294,9 +325,27 @@ private static PointData[] Simplify(ReadOnlySpan<PointF> points, bool isClosed,
294325 {
295326 int next = WrapArrayIndex ( i + 1 , polyCorners ) ;
296327 PointOrientation or = CalculateOrientation ( lastPoint , points [ i ] , points [ next ] ) ;
297- if ( or == PointOrientation . Collinear && next != 0 )
328+ if ( removeCloseAndCollinear && or == PointOrientation . Collinear && next != 0 )
298329 {
299- continue ;
330+ // Preserve collinear points that represent a direction reversal (U-turn)
331+ // within a single segment. E.g. (10,10)→(90,10)→(20,10): the middle point
332+ // is collinear but the stroker needs to see the reversal.
333+ // Don't preserve reversals at segment boundaries — these arise from joining
334+ // different path segments (e.g. arc-to-arc) and are not user-intended.
335+ bool preserve = false ;
336+ if ( linearReversalIndices == null || linearReversalIndices . Contains ( i ) )
337+ {
338+ Vector2 incoming = ( Vector2 ) points [ i ] - lastPoint ;
339+ Vector2 outgoing = ( Vector2 ) points [ next ] - ( Vector2 ) points [ i ] ;
340+ float inLen = incoming . Length ( ) ;
341+ float outLen = outgoing . Length ( ) ;
342+ preserve = inLen > Epsilon && outLen > Epsilon && Vector2 . Dot ( incoming , outgoing ) < 0 ;
343+ }
344+
345+ if ( ! preserve )
346+ {
347+ continue ;
348+ }
300349 }
301350
302351 results . Add (
0 commit comments