@@ -657,21 +657,71 @@ public static string FixUpperTags(string input)
657657 return input ;
658658 }
659659
660- var text = input ;
661- var idx = text . IndexOfAny ( UppercaseTags , StringComparison . Ordinal ) ;
662- while ( idx >= 0 )
660+ // Single forward pass - the old rescan-from-zero loop did eight IndexOf passes plus
661+ // two full-string copies per fixed tag, and this runs per line from AutoBreakLine.
662+ char [ ] chars = null ;
663+ var i = 0 ;
664+ while ( i < input . Length )
663665 {
664- var endIdx = text . IndexOf ( '>' , idx + 2 ) ;
665- if ( endIdx < idx )
666+ if ( input [ i ] == '<' && StartsWithUppercaseTag ( input , i ) )
666667 {
667- break ;
668+ var endIdx = input . IndexOf ( '>' , i + 2 ) ;
669+ if ( endIdx < 0 )
670+ {
671+ break ;
672+ }
673+
674+ chars ??= input . ToCharArray ( ) ;
675+ for ( var k = i ; k < endIdx ; k ++ )
676+ {
677+ chars [ k ] = char . ToLowerInvariant ( chars [ k ] ) ;
678+ }
679+
680+ i = endIdx + 1 ;
681+ continue ;
668682 }
669683
670- var tag = text . Substring ( idx , endIdx - idx ) . ToLowerInvariant ( ) ;
671- text = text . Remove ( idx , endIdx - idx ) . Insert ( idx , tag ) ;
672- idx = text . IndexOfAny ( UppercaseTags , StringComparison . Ordinal ) ;
684+ i ++ ;
673685 }
674- return text ;
686+
687+ return chars == null ? input : new string ( chars ) ;
688+ }
689+
690+ private static bool StartsWithUppercaseTag ( string input , int index )
691+ {
692+ // Matches UppercaseTags: <I> <U> <B> <FONT </I> </U> </B> </FONT>
693+ var remaining = input . Length - index ;
694+ if ( remaining < 3 )
695+ {
696+ return false ;
697+ }
698+
699+ var c1 = input [ index + 1 ] ;
700+ if ( c1 == 'I' || c1 == 'U' || c1 == 'B' )
701+ {
702+ return input [ index + 2 ] == '>' ;
703+ }
704+
705+ if ( c1 == 'F' )
706+ {
707+ return remaining >= 5 && input [ index + 2 ] == 'O' && input [ index + 3 ] == 'N' && input [ index + 4 ] == 'T' ;
708+ }
709+
710+ if ( c1 == '/' && remaining >= 4 )
711+ {
712+ var c2 = input [ index + 2 ] ;
713+ if ( c2 == 'I' || c2 == 'U' || c2 == 'B' )
714+ {
715+ return input [ index + 3 ] == '>' ;
716+ }
717+
718+ if ( c2 == 'F' )
719+ {
720+ return remaining >= 7 && input [ index + 3 ] == 'O' && input [ index + 4 ] == 'N' && input [ index + 5 ] == 'T' && input [ index + 6 ] == '>' ;
721+ }
722+ }
723+
724+ return false ;
675725 }
676726
677727 /// <summary>
@@ -1399,18 +1449,19 @@ public static SKColor GetColorFromString(string s)
13991449 /// </summary>
14001450 /// <param name="input">The string from which to remove color tags.</param>
14011451 /// <returns>A new string with color tags removed.</returns>
1452+ private static readonly Regex ColorAttributeRegex = new Regex ( "[ ]*(COLOR|color|Color)=[\" ']*[#\\ dA-Za-z]*[\" ']*[ ]*" , RegexOptions . Compiled ) ;
1453+
14021454 public static string RemoveColorTags ( string input )
14031455 {
1404- var r = new Regex ( "[ ]*(COLOR|color|Color)=[ \" ']*[# \\ dA-Za-z]*[ \" ']*[ ]*" ) ;
1456+ var r = ColorAttributeRegex ;
14051457 var s = input ;
14061458 var match = r . Match ( s ) ;
14071459 while ( match . Success )
14081460 {
14091461 s = s . Remove ( match . Index , match . Value . Length ) . Insert ( match . Index , " " ) ;
14101462 if ( match . Index > 4 )
14111463 {
1412- var font = s . Substring ( match . Index - 5 ) ;
1413- if ( font . StartsWith ( "<font >" , StringComparison . OrdinalIgnoreCase ) )
1464+ if ( string . Compare ( s , match . Index - 5 , "<font >" , 0 , 7 , StringComparison . OrdinalIgnoreCase ) == 0 )
14141465 {
14151466 s = s . Remove ( match . Index - 5 , 7 ) ;
14161467 var endIndex = s . IndexOf ( "</font>" , match . Index - 5 , StringComparison . OrdinalIgnoreCase ) ;
@@ -1447,6 +1498,11 @@ public static string RemoveColorTags(string input)
14471498 return s . Trim ( ) ;
14481499 }
14491500
1501+ private static readonly Regex FontFaceAttributeRegex = new Regex ( "[ ]*(FACE|face|Face)=[\" ']*[\\ d\\ p{L} ]*[\" ']*[ ]*" , RegexOptions . Compiled ) ;
1502+ private static readonly Regex AssaFontNameOnlyTagRegex = new Regex ( "{\\ \\ fn[a-zA-Z \\ d]+}" , RegexOptions . Compiled ) ;
1503+ private static readonly Regex AssaFontNameLastTagRegex = new Regex ( "\\ \\ fn[a-zA-Z \\ d]+}" , RegexOptions . Compiled ) ;
1504+ private static readonly Regex AssaFontNameInnerTagRegex = new Regex ( "\\ \\ fn[a-zA-Z \\ d]+\\ \\ " , RegexOptions . Compiled ) ;
1505+
14501506 /// <summary>
14511507 /// Remove font tag from HTML or ASSA.
14521508 /// </summary>
@@ -1457,24 +1513,23 @@ public static string RemoveFontName(string input)
14571513 var x = input ;
14581514 if ( x . Contains ( "\\ fn" ) )
14591515 {
1460- x = Regex . Replace ( x , "{ \\ \\ fn[a-zA-Z \\ d]+}" , string . Empty ) ;
1461- x = Regex . Replace ( x , " \\ \\ fn[a-zA-Z \\ d]+}" , "}" ) ;
1462- x = Regex . Replace ( x , " \\ \\ fn[a-zA-Z \\ d]+ \\ \\ " , "\\ " ) ;
1516+ x = AssaFontNameOnlyTagRegex . Replace ( x , string . Empty ) ;
1517+ x = AssaFontNameLastTagRegex . Replace ( x , "}" ) ;
1518+ x = AssaFontNameInnerTagRegex . Replace ( x , "\\ " ) ;
14631519 }
14641520
14651521 return x ;
14661522 }
14671523
1468- var r = new Regex ( "[ ]*(FACE|face|Face)=[ \" ']*[ \\ d \\ p{L} ]*[ \" ']*[ ]*" ) ;
1524+ var r = FontFaceAttributeRegex ;
14691525 var s = input ;
14701526 var match = r . Match ( s ) ;
14711527 while ( match . Success )
14721528 {
14731529 s = s . Remove ( match . Index , match . Value . Length ) . Insert ( match . Index , " " ) ;
14741530 if ( match . Index > 4 )
14751531 {
1476- var font = s . Substring ( match . Index - 5 ) ;
1477- if ( font . StartsWith ( "<font >" , StringComparison . OrdinalIgnoreCase ) )
1532+ if ( string . Compare ( s , match . Index - 5 , "<font >" , 0 , 7 , StringComparison . OrdinalIgnoreCase ) == 0 )
14781533 {
14791534 s = s . Remove ( match . Index - 5 , 7 ) ;
14801535 var endIndex = s . IndexOf ( "</font>" , match . Index - 5 , StringComparison . OrdinalIgnoreCase ) ;
0 commit comments