Caracteres!

O leitor Vitalino Victor, lá do grupo “C & Assembly para arquitetura x86-64”, no Facebook, pergunta: “Como um sistema operacional consegue renderizar os caracteres em um terminal?”. Por “terminal” entendo o modo texto, default, não um terminal numa janela num ambiente gráfico… Mas, vou tentar responder isso das duas maneiras… Só alerto que o que vai na primeira parte deste texto vale apenas para o modo real, para o MS-DOS. No modo protegido teríamos que lidar diretamente com os registradores da placa de vídeo que, por questões de compatibilidade, suporta as mesmas portas de I/O do padrão VGA e XGA. Isto está fora do escopo deste artigo…

Modo texto:

No modo texto, um conjunto de bytes é fornecido para a placa de vídeo, onde cada bit corresponde a um bit aceso ou apagado. No modo padrão, 80×25, um caracter é composto de uma matriz de 8×16 pixels. Assim, o “modo texto” padrão é, na verdade, um modo gráfico com resolução de 640×400.

Por “modo default” quero dizer o modo 3 de vídeo, onde temos 25 linhas e 80 colunas de texto e o tamanho, em pixels (“PELs” na literatura técnica) é de 8×16 PELs. Nesse modo podemos ter 16 cores “de frente” e 8 “de fundo”. Inicializar esse modo é tão simples como fazer:

  mov ax,3
  int 0x10

Esse é dito “modo texto” porque a memória de vídeo é configurada para o formato onde cada 2 bytes contém o par (char,attrib). Onde, char é o código do caracter e attrib um byte contendo a cor de frente, de fundo e um bit de “piscagem”. Nesse modo o código do caracter aparece antes (no endereço par) do atributo (no endereço impar).

A memória de vídeo, usada nesse modo, começa no endereço 0xb8000 (ou, na notação segmento:offset, em 0xb800:0) e tem, exatamente, 4000 bytes (160×25). Se você quiser apagar a tela toda de forma rápida, por exemplo, basta fazer:

clear_screen:
  push es
  mov  ax,0xb800
  mov  es,ax
  xor  di,di
  mov  ax,0x0720   ; AL = ' ';
                   ; AH = branco com fundo preto.
  mov  cx,4000
  rep  stosw
  pop  es
  ret

Mas, de onde ficam as definições dos bitmaps dos caracteres?

A BIOS contém alguns… Existem serviços da software interrupt 0x10 que obtém ou ajustam essa tabela, permitindo ao programador mudar a aparência de caracteres. Isso é feito, por exemplo, pelo device driver DISPLAY.SYS e pelo programa MODE, quando você especifica um charset (CP850 para PT-BR, por exemplo). Eis como o caracter ‘6’ é definido para o modo texto:

...
 { /* ASCII: 54 */
    0x00, /* ········ */
    0x00, /* ········ */
    0x38, /* ··■■■··· */
    0x60, /* ·■■····· */
    0xc0, /* ■■······ */
    0xc0, /* ■■······ */
    0xfc, /* ■■■■■■·· */
    0xc6, /* ■■···■■· */
    0xc6, /* ■■···■■· */
    0xc6, /* ■■···■■· */
    0xc6, /* ■■···■■· */
    0x7c, /* ·■■■■■·· */
    0x00, /* ········ */
    0x00, /* ········ */
    0x00, /* ········ */
    0x00  /* ········ */
  },
...

A tabela completa dos caracteres 8×16 tem 4 kB de tamanho, já que para cada um deles existem 16 bytes que os definem… A listagem acima foi obtida com o auxílio deste programinha, executado num MS-DOS 6.22 configurado com o charset CP850. Ahhh… eu não o testei no Turbo C 2.01 (disponivel aqui), mas sim no Borland C++ 3.1 with Application Frameworks (que é mais difícil de achar!). O código não funcionará com o DJGPP (aqui) ou se for compilado com algum compilador C da Microsoft (exceto, talvez, o Microsoft C Compiler 6.0, mas boa sorte ao tentar achá-lo!).

Para alterar a tabela, modificando a aparência de um ou todos os caracteres, basta usar o serviço 0x1110 a software interrupt 0x10, usando o par ES:BP para apontar para a nova tabela, CX para dizer quantos caracteres serão alterados, BH=16 (16 bytes por caracter), BL=0 (explico adiante!) e DX contendo o código do primeiro caracter da tabela (geralmente 0). Ao incializar esses registradores e chamar “int 0x10” uma nova tabela de definição de caracteres é ajustada na sua placa de vídeo…

O valor de BL indica um número de tabela… Isso existe porque, na prática, o bit 3 dos atributos, na memória de vídeo, pode ser usado para modificar a tabela em uso. Assim, podemos ter 2 tabelas, com caracteres diferentes, totalizando 512 caracteres possíveis. Claro que isso diminui a quantidade de cores “de frente” do modo texto!

Ambientes gráficos (GUIs):

Quando você está lidando com ambientes gráficos a BIOS está completamente “fora da jogada”. Esses ambientes geralmente funcionam sob o modo protegido e a placa de vídeo não tem suporte direto para eles. Não me entenda mal, as placas de vídeo modernas dão suporte a uma série de facilidades que são usadas por esses ambientes gráficos, mesmo no âmbito da computação gráfica 2D. Não é preciso preencher um retângulo pixel por pixel, por exemplo… Basta informar à placa: Desenhe um retângulo branco entre (x1,y1) e (x2,y2) e voilà! A mesma coisa acontece quando queremos copiar retângulos…

No modo gráfico textos têm que ser desenhados, assim, um método de desenhar texto é copiar retângulos de um bitmap como este:

Fonte monoespaçada em formato bitmap.
Fonte monoespaçada em formato bitmap.
Diferença entre 'I' e 'M'.
Diferença entre ‘I’ e ‘M’..

Aqui temos um retângulo contendo pixels para cada um dos 26 caracteres maiúsculos do alfabeto. Se quisermos desenhar um “C” basta calcular a posição do retângulo deste caracter e copiá-lo para a memória de vídeo na posição desejada. Note que, neste caso, todos os caracteres têm o mesmo tamnho horizontal e, por isso, essa fonte é chamada de monoespaçada. Outras fontes, como Arial, por exemplo, não são monoespaçadas… A letra ‘I’ tem largura menor que ‘M’… Neste outro tipo de fonte temos que usar um algoritmo mais refinado…

O modo gráfico nos dá algumas vantagens: Pode não parecer evidente, mas os caracteres na figura acima têm bordas suavizadas. Um meio de fazer isso é usando 4 componentes de cor, ao invés dos 3 componentes tradicionais (RGB) adicionamos um Alpha, que nos diz a quantidade de transparência de um pixel. A letra ‘C’, no gráfico acima, pode ser vista, expandida, assim:

'C' com transparência.
‘C’ com transparência.

Por causa do canal alpha podemos misturar (blend) os pixels da fonte com os pixels da imagem de fundo sem perder o efeito de suavização da fonte.

Acontece que esse método tem um problema sério! Para termos fontes de tamanho diferentes teríamos que escalonar o bitmap e, ao fazer isso, a aparência da fonte mudaria muito para escalar pequenas ou muito grandes… O “zoom” acima, da letra ‘C’, mosra o efeito… Esse ‘C’ expandido é bem diferente do ‘C’ no bitmap original, não?

Para evitar isso as “fontes” não são armazenadas como bitmaps. Elas são armazenadas como curvas, como equações matemáticas… O seu ambiente gráfico pega essas equações, aplica um algoritmo que as desenha num bitmap, em memória, e depois copia o desenho feito para a memória de vídeo…

Para que isso funcione, além da codificação matemática da fonte, um sistema de métricas têm que ser adotado. A figura abaixo mostra algumas dessas “métricas”:

Sistema de métricas para TrueType
Sistema de métricas para TrueType

Geralmente as fontes são agrupadas em arquivos diferentes para negrito ou itálico, mas elas obedecem às métricas porque uma fonte “reta” pode ser calculada para ser transformada em itálica com ângulos diferentes da métrica.

Tipografia:

Essas métricas não são arbitrarias… elas seguem um padrão tipográfico universal… Por exemplo, o tamanho de uma fonte (tamanho 12, por exemplo) é medido em “pontos”, que equivale a \frac{1}{72} de uma polegada ou ~0,353 mm (este é o padrão americano, o europeu é diferente!). Assim, uma fonte de tamanho 12 tem tamanho (altura) de 4,23 mm (ou \frac{1}{6} de polegada), tanto numa folha de papel quanto num monitor de vídeo, calibrado, com zoom de 1:1.

Outra medida tipográfica é a “paica” (pica em inglês, sem trocadilhos!), que equivale a \frac{1}{6} de polegada. É uma unidade muito usada pela turma que lida com diagramação…

É de espantar que a medida mais imprecisa possível é o “pixel”! Dependendo do dispositivo, um pixel pode ser grande ou pequeno… Um “pixel” em um monitor com resolução Full HD é bem menor que um na resolução VGA de 640×480 e ambos são muito maiores que um pixel impresso numa folha de papel por uma impressora Laser (já que monitores de vídeo costumam ter resolução de 72 ou 96 dpi e impressoras Laser de 150 a 1200 dpi ou mais)… Além disso, o pixel pode ter geometrias diferentes: Numa resolução 640×480, num monitor com aspect ratio 4:3, um pixel é perfeitamente quadrado, mas na mesma resolução, num monitor 16:9 ele é “achatado”. Em resoluções que não correspondam diretamente ao aspect ratio do monitor o pixel é claramente “não quadrado”… Infelizmente “pixel” é a unidade default usada por HTML quando lida com imagens, embora use “pontos” quando lida com fontes…

E, por falar em HTML, alguns sufixos podem ser aplicados aos tamanhos para informat a unidade: “px” para “pixel”, “pt” para ponto, “pc” para “paica” (como definidos acima). Esses prefixos indicam tamanhos “absolutos”, que podem ser medidos diretamente, assim como “cm” (centímetro), “mm” (milímetro) e “in” (“inches” ou polegadas). Existem medidas relativas como “em” (de “element”) que levam em conta um tamanho “default” de uma fonte… Se a fonte foi construída de forma que 1 em for igual a 1 mm, então 10 em ocupará 10 mm… Mas, note, isso depende da qualidade da fonte e pode dar um monte de dores de cabeça num design gráfico dependente de tamanhos.

O quão preciso é a métrica do monitor?

Acredite: É bem preciso… Eis um exemplo que obtive num HP Compaq Elite 8300:

$ xrandr -q
Screen 0: minimum 320 x 200, current 1920 x 1080, maximum 8192 x 8192
eDP1 connected primary 1920x1080+0+0 (normal left inverted right x axis y axis) 510mm x 287mm

Peguei uma régua e medi o monitor e obtive exatamente a medição acima (510×287 mm) que é “quase” correspondete ao aspect ratio 16:9.

Essas informações estão disponíveis para o sistema operacional graças ao padrão VESA EDID (Extended Display Identification Data) que todo monitor digital disponibiliza. Com isso, é possível calcular exatamente o tamanho de fontes, interpolando valores, se necessário!

Outros problemas ao lidar com fontes:
Além da complexidade da codificação e das métricas, todo diagramador sabe que existem outros problemas interessantes como o ajuste de uma linha de texto num parágrafo, por exemplo. Repare que nos meus textos costumo usar o método mais simples possível: Ajuste de linhas pela esquerda… Há quem prefira a “justificação” do texto, assim:

A formatação “justificada” é mais confortável que a alinhada à esquerda. Ela existe por esse motivo, assim como as “serifas” em certas fontes (os “pézinhos” dos caracteres)… Mas a justificação tem lá seus problemas, como cito abaixo.

Fica bem legal de ler, mas a distância entre as palavras fica variável e corre-se o risco de termos linhas com menos palavras do que as demais, se usarmos palavras grandes:

Se tivermos palavras grandes numa mesma sentença cheia de palavras pequenas, acabamos com um texto desengonçado… Por exemplo, se colocarmos  Pneumoultramicroscopicossilicovulcanoconióticos, pneumoultramicroscopicossilicovulcanoconióse e oftalmotorrinolaringologista numa mesma sentença (e são palvras que existem em nossos dicionários) elas bagunçam o coreto… Viu só?

Também, por causa dos tamanhos variáveis de cada um dos caracteres definidos em fontes não monoespaçadas, obter o tamanho de uma “string” só é possível depois que ela for pré-renderizada ou, se o algorítmo levar em conta a métrica individual de cada um dos caracteres… Ou seja, se você quer calcular o tamanho ocupado por uma string numa janela, não pode simplesmente fazer uma chamada a strlen() e multiplicar por alguma constante!

Esses são alguns motivos pelos quais lidar com fontes em ambientes gráficos não é tarefa trivial…

Anúncios