Reprezentacja bitowa znaków Unicode i UTF-8

Jakiś taki naukowy ten tytuł wyszedł. Ale inaczej się chyba nie da. Potrze­bo­wa­łem pretek­stu żeby się trochę poba­wić w mani­pu­lo­wa­nie bitami. Padło na Unicode z racji fajnego sposobu, w jaki wymy­ślono sam zapis znaków. Jeśli jest jesz­cze ktoś kto nie wie co to jest Unicode to zapra­szam do źródła.

Wszystko zostało tak prze­my­ślane, że im bardziej pokrę­cony język, tym więcej miej­sca potrzeba na jego zapi­sa­nie. Wszyst­kie liczby i litery bez ogon­ków damy radę zapi­sać w postaci 1 bajta. Chodziło o kompa­ty­bil­ność z forma­tem ASCII. Znaczki języka polskiego znaj­dują się w dziale Latin Extended-A. Zapis znaków polskich zajmie 2 bajty w forma­cie UTF-8.

Mając numer znaku z tabeli Unicode najła­twiej wyświe­tlić go za pomocą wbudo­wa­nej w PHP funk­cji html_entity_decode():

echo html_entity_decode('&#' . 0xA7 . ';', ENT_NOQUOTES, 'UTF-8');

Wywo­ła­nie powyż­szego wier­sza spowo­duje wyświe­tle­nie znaku o kodzie 0xA7 — para­grafu §. W jaki więc sposób wyko­nuje się czary-mary i znak Unicode staje się znakiem UTF-8? Weźmy na warsz­tat znak ę opisany w tablicy jako latin small letter e with ogonek. Ma numer 0119 (heksa­de­cy­mal­nie!), czyli można zapi­sać tak:
0×11916 = 28110 = 1000110012
Z powyż­szego widać, że liczbę dzie­siętną 281 możemy zapi­sać w postaci 9 bitów. Doku­men­ta­cja funk­cji utf8_encode() zawiera tabelkę ile bitów znaku zmie­ścimy w ilu bajtach UTF-8. Wygląda na to, że w jedno­baj­to­wym zapi­sie zmie­ścimy znaki o nume­rach od 0 do 12710 (7 bitów). Za to dyspo­nu­jąc 2 bajtami zapi­szemy liczby aż do 204710, czyli w zupeł­no­ści nam wystarczy.

Znak ę w UTF-8 zapi­su­jemy w 2 bajtach, czyli do liczby 110000002 musimy dodać prze­su­niętą o 6 bitów w prawo liczbę 281 (ponie­waż pójdą do drugiego bajtu znaku). Z drugiego wyrzu­camy wszystko poza 6 ostat­nimi bitami, a następ­nie doda­jemy do liczby 100000002.

$uniChar = 0x119;
$byte1 = $uniChar >> 6 | 0xC0;
$byte2 = $uniChar & 0x3F | 0x80

Wyszło na to, że ę w zapi­sie UTF-8 to będzie 11000100 10011001. Po ubra­niu tego wszyst­kiego w funkcję:

function hexToUTF8HexArray($hexNum)
{
   // konwertujemy na liczbe w razie czego
   if (is_string($hexNum)) $hexNum = hexdec($hexNum);
 
   $hexArray = array();
   if ($hexNum < 0x80) {
      $hexArray[0] = '0x' . dechex($hexNum);
      return $hexArray;
   } elseif ($hexNum < 0x800) {
      $hexArray[0] = '0x' . dechex($hexNum >> 6 | 0xC0);
      $hexArray[1] = '0x' . dechex($hexNum & 0x3F | 0x80);
      return $hexArray;
   } else {
      throw new Exception('Not supported');
   }
}

Wywo­łu­jąc hexToUTF8HexArray(0x119) da nam tablicę z warto­ściami 0xc4 i 0×99. No dobra, a co jak chcę odwró­cić proces? Teraz będzie przyjemniej:

function utf8ToUnicode(array $utf8Array)
{
   // konwertujemy na liczby
   $utf8ArrayChecked = array();
   foreach ($utf8Array as $utf8) {
      if (is_string($utf8)) $utf8 = hexdec($utf8);
      $utf8ArrayChecked[] = $utf8;
   }
 
   $bytesCount = count($utf8ArrayChecked);
   switch ($bytesCount) {
      case 1:
         return $utf8ArrayChecked[0];
      case 2:
         // wyrzucamy naglowki
         $b1 = $utf8ArrayChecked[0] & 0x1F;
         $b2 = $utf8ArrayChecked[1] & 0x3F;
 
         // tutaj cala magia
         $number = $b1 << 6 | $b2;
         return '0x' . dechex($number);
      default:
         throw new Exception('Not supported'); 
   }
}

Wywo­łu­jąc utf8ToUnicode(array(0xc4, 0x99)) otrzy­mamy 0×119, czyli gra jak trzeba.

Na koniec cieka­wostka: mając jakiś znak możemy łatwo wydo­być jego wartość UTF-8.

function charToUTF8HexArray($char)
{
   $i = 0;
   $hexArray = array();
   while (isset($char[$i])) {
      $hexArray[] = '0x' . dechex(ord($char[$i++]));
   }
   return $hexArray;
}

charToUTF8HexArray('ę') da nam tablicę 0xc4, 0×99.

Ten wpis to taka trochę notatka dla mnie, przez co może trochę chaotycz­nie napisany…

Podobne wpisy:

  1. MPlayer i wyświe­tla­nie polskich znaków
  2. Przy­kład wyko­rzy­sta­nia domknięć w PHP
  3. A Ty w jaki sposób łączysz się z bazą danych?
  4. Tworze­nie pakie­tów ICMPPHP

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.

Możesz użyć następujących tagów oraz atrybutów HTML-a: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <p> <pre lang="" line="" escaped=""> <q cite=""> <strike> <strong>