@@ -2663,6 +2663,22 @@ class SVGLoader extends Loader {
26632663 outerPoint . copy ( tempV2_5 ) . add ( currentPoint ) ;
26642664 innerPoint . add ( currentPoint ) ;
26652665
2666+ // in-loop fold detection to mitigate #25326
2667+ if ( innerSideModified ) {
2668+
2669+ // when the second triangle's signed area would flip, snap innerPoint to the previous inner-side vertex
2670+
2671+ const refPt = joinIsOnLeftSide ? lastPointR : lastPointL ;
2672+ const foldCross = ( outerPoint . x - refPt . x ) * ( innerPoint . y - refPt . y )
2673+ - ( outerPoint . y - refPt . y ) * ( innerPoint . x - refPt . x ) ;
2674+ if ( ( joinIsOnLeftSide && foldCross < 0 ) || ( ! joinIsOnLeftSide && foldCross > 0 ) ) {
2675+
2676+ innerPoint . copy ( refPt ) ;
2677+
2678+ }
2679+
2680+ }
2681+
26662682 isMiter = false ;
26672683
26682684 if ( innerSideModified ) {
@@ -2941,6 +2957,32 @@ class SVGLoader extends Loader {
29412957
29422958 }
29432959
2960+ // Second fix for #25326: Scan for reamining flipped (CW) triangles and collapse them to
2961+ // degenerated ones. This is safe and leaves no "holes" in the stroke because the flipped
2962+ // triangle's area is covered by neighbouring (CCW) triangles.
2963+
2964+ if ( vertices ) {
2965+
2966+ const tri = [ new Vector2 ( ) , new Vector2 ( ) , new Vector2 ( ) ] ;
2967+ const startFloat = vertexOffset * 3 ;
2968+
2969+ for ( let t = startFloat ; t < currentCoordinate ; t += 9 ) {
2970+
2971+ tri [ 0 ] . set ( vertices [ t ] , vertices [ t + 1 ] ) ;
2972+ tri [ 1 ] . set ( vertices [ t + 3 ] , vertices [ t + 4 ] ) ;
2973+ tri [ 2 ] . set ( vertices [ t + 6 ] , vertices [ t + 7 ] ) ;
2974+
2975+ if ( ShapeUtils . area ( tri ) < 0 ) {
2976+
2977+ vertices [ t + 3 ] = tri [ 0 ] . x ;
2978+ vertices [ t + 4 ] = tri [ 0 ] . y ;
2979+
2980+ }
2981+
2982+ }
2983+
2984+ }
2985+
29442986 return numVertices ;
29452987
29462988 // -- End of algorithm
0 commit comments