Porque usar UTF-8 - Codificando/Decodificando

Bom galera, como prometido venho hoje aqui demonstrar como o UTF-8 realmente funciona por debaixo dos panos, e vamos criar uma implementação do mesmo utilizando JavaScript.

Como eu já havia dito antes, o UTF-8 usa uma codificação de tamanho variável, onde cada caractere pode ocupar entre 1 e 4 bytes, para que isso seja possível, existem bits que são usados para verificar isso, vamos ver o primeiro caso:

Eu falei para vocês anteriormente que para os valores ASCII padrão o UTF-8 mantém a compatibilidade, ou seja, não existe nenhum transformação para os caracteres entre 0 e 127, se você olhar o binário disso, vai perceber que isso varia entre:
00000000
01111111

É importante notar que o primeiro bit do byte não é modificado, isso é importante porque ele é flag que indica se vamos precisar de mais bytes no caractere, então, a partir do valor 128 temos que utilizar algum tipo de transformação.

Antes de proseguirmos com os detalhes vamos iniciar nosso script, comecem ele da seguinte forma:


Esse é um simples script que por hora não faz nada :P

Mas ele pega o valor do campo de cima e mostra esse valor codificado no campo de baixo, assim como pega o campo de baixo e joga decoficidado no campo de cima. A partir de agora só iremos trabalhar nos métodos encode e decode do objeto UTF8 que criamos acima.

Voltando ao UTF-8, vamos criar primeiro o codificador, então vamos continuar o entendimento dos bytes.

A partir do momento que o primeiro bit deveria ser usado, então a coisa muda, imaginando um caso simples, o caractere de numero 128, se continuassemos usando a maneira simples de somar bits teriamos a seguinte sequencia de bits:
10000000
Mas para o UTF-8 a sequencia correta seria:
11000010 10000000
Parece complicado, mas não é para tanto, quando passamos dos 128 caracteres iniciais temos alguns "moldes" onde devemos encaixar nossos bits, que no caso são os seguintes (por uso de bytes):
1 byte - 0xxxxxxx
2 bytes - 110xxxxx 10xxxxxx
3 bytes - 1110xxxx 10xxxxxx 10xxxxxx
4 bytes - 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

Esses moldes não são ao acaso, e como pode-se ver eles seguem um padrão, esses moldes são importantes para que o precesso de decodificação, notem que a partir dos primeiros bits do primeiro byte é possivel saber exatamente quantos bytes tem o caractere atual.

Para criamos o processo de encoding, o primeiro passo é descobrir quantos bytes serão nescessários para criar nosso caractere no formato UTF-8, para saber isso é só contar os limites a partir do número de bits que podem ser alocados em cada nível. Mas como eu sou muito legal eu já vou disponibilar esses números para você, e são eles: 128, 2048, 65536

Pra quem nunca executou operações bit a bit pode parecer um pouco complicado sair mexendo eles por ai, mas é facil, primeiro mantenha a calculadora do windows aberta (em modo científico, assim você pode converter entre decimal, hexadecimal e binario sempre que precisar, e você vai precisar), então vamos criar a função de encode:


Pra quem não está acostumado com manipulação de bits isso pode parecer coisa de louco, mas é simples, vou ilustrar como funcionam os operadores bit a bit utilizados nas operações:

right shift (>>): esse operador simplesmente move bits a direita, caso o bit ultrapasse o primeiro ele é descartado, exemplos:
00011000 >> 2
resulta em: 00000110
01101110 >> 3
resulta em: 00001101

ou seja, eh simplesmente mover bits a direita (se lembre, tudo isso é forma de representação, tudo são bits, desde numeros a caracteres, vc não vai digitar em formato binário nunca, pelo menos não em javascript)

E binário (&): executa uma operação E entre binários, a idéia é simples, você coloca os bits que são comparados em uma listagem de 1 para 1, caso o valor seja 1 nos 2, o resultádo será 1, caso contrário será 0, exemplos:
  00110111
& 01100010
----------
  00100010

  01111000
& 00111110
----------
  00111000

esse operador é muito usado para mascarar bits, por exemplo, você quer apenas os 4 últimos bits de um dado binário, então você opera um E contendo 00001111 sobre esse binário, dessa forma você terá o resultado com os 4 últimos valores.

OU binário (|): esse é parecido com o anterior, mas com uma diferença básica, esse retorn 1 exceto se os 2 operadores forem 0, exemplo:
  00110101
| 11100001
----------
  11110101

Isso são alguns operadores binários, caso não tenha ficado muito claro podem tirar suas dúvidas comigo por comentários ou e-mail.

Dever de casa: pegue os números utilizados no algoritmo anterior em formato hexadecimal (que comecam com 0x) e veja suas formas em binário (use a calculadora do windows ou similar), e va executando o algoritmo como se fosse o computador, dessa forma você terá um melhor entendimento do algoritmo.

O encode está pronto, agora precisamos pegar os códigos Unicode a partir disso que geramos, então vamos criar o decode:

O processo inverso é exatamente a mesma coisa, é só pegar os bits que você jogou no formato e junta-los novamente. Para descobrir quantos bytes são usados eu utilizei a seguinte idéia:

1 - se for menor que 128, então é padrão ASCII, jogar direto
2 - verificar bit zero dentro do primeiro byte, a partir disso é possível descobrir o número (se baseando nos formatos possíveis).

O único operador novo é o left shift (<<) que simplesmente move os bits para esquerda ;)

Bom galera, termino por aqui, qualquer dúvida entrem em contato comigo por e-mail ou comentários.

Mudarei de assunto no próximo post (assunto indefinido até o momento ;).

See yah!

Você pode conferir abaixo o resultado final:

Published: October 20 2008

  • category:
  • tags: