Юникодная елка
Лишь годные дятлы собираются в стаи,
Юникодом пугая мозги января.
Их песни не стихнут, они не устанут.
А елка как кактус беспокоит меня.
Как испортить кашу
Юникодные преобразования в Delphi почти невозможно ускорить. Но можно выбросить из головы все плоскости, кроме нулевой, транжирить память на таблицы, напрягать ее пересылками, наплевать на особые случаи и ошибки. Можно быть проще, короче.
AnsiToUnicode и UnicodeToAnsi
Функции ShaAnsiToUnicode и ShaUnicodeToAnsi довольно незатейливы, они быстрее аналогов за счет развернутого цикла и таблиц. Здесь и далее типы NInt и UInt — знаковое и беззнаковое нативное целое.
function ShaAnsiToUnicode(Dest: PWideChar; Source: PAnsiChar; SourceChars: NInt; pTable: PConvTable): PWideChar; overload; var Tail: PAnsiChar; begin; if NInt(SourceChars)>0 then begin; Tail:=Source + SourceChars + (-4); if Source<=Tail then repeat; Dest[0]:=WideChar(pTable.AnsiToWide[byte(Source[0])]); Dest[1]:=WideChar(pTable.AnsiToWide[byte(Source[1])]); Dest[2]:=WideChar(pTable.AnsiToWide[byte(Source[2])]); Dest[3]:=WideChar(pTable.AnsiToWide[byte(Source[3])]); inc(Source, 4); inc(Dest, 4); until Source>Tail; inc(Tail, 4); if Source<Tail then repeat; Dest[0]:=WideChar(pTable.AnsiToWide[byte(Source[0])]); inc(Source); inc(Dest); until Source>=Tail; end; Result:=Dest; end;
function ShaUnicodeToAnsi(Dest: PAnsiChar; Source: PWideChar; SourceChars: NInt; pTable: PConvTable): PAnsiChar; overload; var Tail: PWideChar; begin; if NInt(SourceChars)>0 then begin; NInt(Tail):=NInt(Source) + SourceChars * SizeOf(WideChar) + (-4 * SizeOf(WideChar)); if Source<=Tail then repeat; Dest[0]:=AnsiChar(pTable.WideToAnsi[word(Source[0])]); Dest[1]:=AnsiChar(pTable.WideToAnsi[word(Source[1])]); Dest[2]:=AnsiChar(pTable.WideToAnsi[word(Source[2])]); Dest[3]:=AnsiChar(pTable.WideToAnsi[word(Source[3])]); inc(Source, 4); inc(Dest, 4); until Source>Tail; inc(Tail, 4); if Source<Tail then repeat; Dest[0]:=AnsiChar(pTable.WideToAnsi[word(Source[0])]); inc(Source); inc(Dest); until Source>=Tail; end; Result:=Dest; end;
UnicodeToUTF8 и AnsiToUTF8
Функции ShaUnicodeToUTF8 и ShaAnsiToUTF8 содержат только одну хитрость: начальные ASCII-символы строки обрабатываются группами. Педаль для моих любимых буковок в них отсутствует, хотя эксперименты показывают, что при сильном желании можно увеличить скорость еще примерно на 10%. В общем, трудно человеку.
function ShaUnicodeToUTF8(Dest: PUTF8Char; Source: PWideChar; SourceChars: NInt): PUTF8Char; overload; var Ch: UInt; Tail: PWideChar; begin; if NInt(SourceChars)>0 then begin; //by 2 WideChars NInt(Tail):=NInt(Source) + SourceChars * SizeOf(WideChar) + (-4); if Source<=Tail then repeat; Ch:=pCardinal(Source)^; if Ch and $FF80FF80<>0 then break; //break if not ASCII pair Ch:=Ch shr 8 or Ch; pWord(Dest)^:=Ch; inc(Source, 2); inc(Dest, 2); until Source>Tail; //by 1 WideChar NInt(Tail):=NInt(Tail) + 4; //Tail = terminator if Source<Tail then while true do begin; Ch:=word(Source[0]); inc(Source); if Ch<=127 then begin; Dest[0]:=AnsiChar(Ch); inc(Dest); if Source<Tail then continue else break; end; if Ch<=$7FF then begin; Dest[0]:=AnsiChar(Ch shr 6 or $C0); Dest[1]:=AnsiChar(Ch and $3F or $80); inc(Dest, 2); if Source<Tail then continue else break; end else begin; Dest[0]:=AnsiChar(Ch shr 12 or $E0); Dest[1]:=AnsiChar(Ch shr 6 and $3F or $80); Dest[2]:=AnsiChar(Ch and $3F or $80); inc(Dest, 3); if Source<Tail then continue else break; end; end; end; Result:=Dest; end;
function ShaAnsiToUTF8(Dest: PUTF8Char; Source: PAnsiChar; SourceChars: NInt; pTable: PConvTable): PUTF8Char; overload; var Ch: UInt; Tail: PAnsiChar; begin; if NInt(SourceChars)>0 then begin; //by 4 AnsiChars Tail:=Source + SourceChars + (-4); if Source<=Tail then repeat; Ch:=pCardinal(Source)^; if Ch and $80808080<>0 then break; //break if not ASCII dword pCardinal(Dest)^:=Ch; inc(Source, 4); inc(Dest, 4); until Source>Tail; //by 1 AnsiChar Tail:=Tail + 4; //Tail = terminator if Source<Tail then while true do begin; Ch:=byte(Source[0]); inc(Source); if Ch<=127 then begin; Dest[0]:=AnsiChar(Ch); inc(Dest); if Source<Tail then continue else break; end; Ch:=pTable.AnsiToWide[Ch]; if Ch<=$7ff then begin; //Ch:=Ch and $003F shl 8 or Ch shr 6 or $80C0; //pWord(Dest)^:=Ch; Dest[0]:=AnsiChar(Ch shr 6 or $C0); Dest[1]:=AnsiChar(Ch and $3F or $80); inc(Dest, 2); if Source<Tail then continue else break; end else begin; Dest[0]:=AnsiChar(Ch shr 12 or $E0); Dest[1]:=AnsiChar(Ch shr 6 and $3F or $80); Dest[2]:=AnsiChar(Ch and $3F or $80); inc(Dest, 3); if Source<Tail then continue else break; end; end; end; Result:=Dest; end;
UTF8ToUnicode и UTF8ToAnsi
Здесь применяется тот же трюк, что и в двух предыдущих функциях. Остальные нагромождения кода в ShaUTF8ToUnicode и ShaUTF8ToAnsi рождены в попытках оптимизировать их для обработки реального текста.
Любите ли вы таблицы? Из-за них скорость ShaUTF8ToAnsi меньше, чем ShaUTF8ToUnicode.
function ShaUTF8ToUnicode(Dest: PWideChar; Source: PUTF8Char; SourceBytes: NInt): PWideChar; overload; var Tail: PUTF8Char; Ch: UInt; begin; if (Source<>nil) and (NInt(SourceBytes)>0) then begin; //1-byte sequences by 4 AnsiChars Tail:=Source + SourceBytes + (-4); //Tail = terminator-4 if Source<=Tail then repeat; Ch:=pCardinal(Source)^; if (Ch + Ch) and Ch and $80808080<>0 then break; //break if multibyte pCardinal(Dest)^:=(Ch shl 8 or Ch and $FF) and $00FF00FF; Ch:=Ch shr 16; inc(Dest, 2); pCardinal(Dest)^:=(Ch shl 8 or Ch) and $00FF00FF; inc(Dest, 2); inc(Source, 4); until Source>Tail; if Source<Tail+4 then begin; //if Source < terminator inc(Tail, 2); //Tail = terminator-2 Ch:=byte(Source[0]); inc(Source); if Ch<$C0 then repeat; Dest[0]:=WideChar(Ch); inc(Dest); Ch:=byte(Source[0]); inc(Source); until (Ch>=$C0) or (Source>=Tail); //multibyte sequences while true do begin; if Source>=Tail then break; //Source<=Tail-1 //2-byte sequences if Ch and $20=0 then begin; //here Ch>=$C0 Ch:=Ch shl 6 + byte(Source[0]) - $00003080; //Tail-1 max inc(Source, 2); //Source<=Tail+1 Dest[0]:=WideChar(Ch); Ch:=byte(Source[-1]); //Tail max inc(Dest); if Ch>=$C0 then continue; //1-byte sequences after 2-byte sequences inc(Dest); inc(Source); //Source<=Tail+2 while true do begin; Dest[-1]:=WideChar(Ch); Ch:=byte(Source[-1]); //Tail+1 max if Ch>=$C0 then break; if Source>Tail then break; //Source<=Tail Dest[0]:=WideChar(Ch); Ch:=byte(Source[0]); //Tail max inc(Dest, 2); inc(Source, 2); //Source<=Tail+2 if Ch<$C0 then continue; dec(Dest); dec(Source); //Source<=Tail+1 break; end; end //3-byte sequences else begin; repeat; //Source<=Tail-1 Ch:=Ch shl 12 + byte(Source[0]) shl 6 + byte(Source[1]) - $000E2080; //Tail max inc(Source, 3); //Source<=Tail+2 Dest[0]:=WideChar(Ch); Ch:=byte(Source[-1]); //Tail+1 max inc(Dest); if Source>=Tail then break; until Ch<$E0; if Ch>=$C0 then continue; //1-byte sequences after 3-byte sequences while true do begin; if Source>Tail then break; //Source<=Tail Dest[0]:=WideChar(Ch); Ch:=byte(Source[0]); //Tail max inc(Dest, 2); inc(Source, 2); //Source<=Tail+2 if Ch<$C0 then begin; Dest[-1]:=WideChar(Ch); Ch:=byte(Source[-1]); //Tail+1 max if Ch<$C0 then continue; end else begin; dec(Dest); dec(Source); //Source<=Tail+1 end; break; end; end; end; //while //handle tail inc(Tail, 2); //Tail = terminator dec(Source); //Source[0] is the first unhandled byte while Source<Tail do begin; Ch:=byte(Source[0]); if Ch<$C0 then inc(Source) else if Ch and $20=0 then begin; //here Ch>=$C0 inc(Source, 2); if Source>Tail then break; Ch:=Ch shl 6 + byte(Source[-1]) - $00003080; end else begin; inc(Source, 3); if Source>Tail then break; Ch:=Ch shl 12 + byte(Source[-2]) shl 6 + byte(Source[-1]) - $000E2080; end; Dest[0]:=WideChar(Ch); inc(Dest); end; end; end; Result:=Dest; end;
function ShaUTF8ToAnsi(Dest: PAnsiChar; Source: PUTF8Char; SourceBytes: NInt; pTable: PConvTable): PAnsiChar; overload; var Tail: PUTF8Char; Ch: UInt; begin; if (Source<>nil) and (NInt(SourceBytes)>0) then begin; //1-byte sequences by 4 AnsiChars Tail:=Source + SourceBytes + (-4); //Tail = terminator-4 if Source<=Tail then repeat; Ch:=pCardinal(Source)^; if (Ch + Ch) and Ch and $80808080<>0 then break; //break if multibyte pCardinal(Dest)^:=Ch; inc(Source, 4); inc(Dest, 4); until Source>Tail; if Source<Tail+4 then begin; //if Source < terminator inc(Tail, 2); //Tail = terminator-2 Ch:=byte(Source[0]); inc(Source); if Ch<$C0 then repeat; Dest[0]:=AnsiChar(Ch); inc(Dest); Ch:=byte(Source[0]); inc(Source); until (Ch>=$C0) or (Source>=Tail); //multibyte sequences while true do begin; if Source>=Tail then break; //Source<=Tail-1 //2-byte sequences if Ch and $20=0 then begin; //here Ch>=$C0 Ch:=Ch shl 6 + byte(Source[0]) - $00003080; //Tail-1 max inc(Source, 2); //Source<=Tail+1 Dest[0]:=AnsiChar(pTable.WideToAnsi[Ch and $0000FFFF]); Ch:=byte(Source[-1]); //Tail max inc(Dest); if Ch>=$C0 then continue; //1-byte sequences after 2-byte sequences inc(Dest); inc(Source); //Source<=Tail+2 while true do begin; Dest[-1]:=AnsiChar(pTable.WideToAnsi[Ch]); Ch:=byte(Source[-1]); //Tail+1 max if Ch>=$C0 then break; if Source>Tail then break; //Source<=Tail Dest[0]:=AnsiChar(pTable.WideToAnsi[Ch]); Ch:=byte(Source[0]); //Tail max inc(Dest, 2); inc(Source, 2); //Source<=Tail+2 if Ch<$C0 then continue; dec(Dest); dec(Source); //Source<=Tail+1 break; end; end //3-byte sequences else begin; repeat; //Source<=Tail-1 Ch:=Ch shl 12 + byte(Source[0]) shl 6 + byte(Source[1]) - $000E2080; //Tail max inc(Source, 3); //Source<=Tail+2 Dest[0]:=AnsiChar(pTable.WideToAnsi[Ch and $0000FFFF]); Ch:=byte(Source[-1]); //Tail+1 max inc(Dest); if Source>=Tail then break; until Ch<$E0; if Ch>=$C0 then continue; //1-byte sequences after 3-byte sequences while true do begin; if Source>Tail then break; //Source<=Tail Dest[0]:=AnsiChar(pTable.WideToAnsi[Ch]); Ch:=byte(Source[0]); //Tail max inc(Dest, 2); inc(Source, 2); //Source<=Tail+2 if Ch<$C0 then begin; Dest[-1]:=AnsiChar(pTable.WideToAnsi[Ch]); Ch:=byte(Source[-1]); //Tail+1 max if Ch<$C0 then continue; end else begin; dec(Dest); dec(Source); //Source<=Tail+1 end; break; end; end; end; //while //handle tail inc(Tail, 2); //Tail = terminator dec(Source); //Source[0] is the first unhandled byte while Source<Tail do begin; Ch:=byte(Source[0]); if Ch<$C0 then begin; inc(Source); end else if Ch and $20=0 then begin; //here Ch>=$C0 inc(Source, 2); if Source>Tail then break; Ch:=Ch shl 6 + byte(Source[-1]) - $00003080; end else begin; inc(Source, 3); if Source>Tail then break; Ch:=Ch shl 12 + byte(Source[-2]) shl 6 + byte(Source[-1]) - $000E2080; end; Dest[0]:=AnsiChar(pTable.WideToAnsi[Ch and $0000FFFF]); inc(Dest); end; end; end; Result:=Dest; end;
Прикрепленный файл ShaUnicode.pas (версия от 05.02.2012) содержит весь этот новогодний бред в одном флаконе, пока это бета для Delphi 7. Вроде, ничего не забыл. Ах, да: ни один дятел не пострадал при тестировании функций.
Прикрепленный файл | Размер |
---|---|
ShaUnicode.pas | 29 кб |