Bem-vindo à primeira parte da Modern Linux Text Rendering. Em cada artigo desta série, desenvolveremos um programa C independente para visualizar um caracter ou sequência de caracteres. Cada um desses programas implementará uma função que considero necessária para a renderização de texto moderna.
Na primeira parte, configuraremos o FreeType e escreveremos um renderizador de símbolos simples no console.

É isso que vamos escrever. E
aqui está o código.
Configuração do sistema
- Meu sistema operacional:
Ubuntu 18.04.2 LTS (bionic)
- Compilador C:
clang version 6.0.0-1ubuntu2
Instale o FreeType
No Ubuntu, você precisa instalar o FreeType e a libpng.
$ sudo apt install libfreetype6 libfreetype6-dev $ sudo apt install libpng16-16 libpng-dev
- Eu tenho a versão
2.8.1-2ubuntu2
FreeType, embora, no momento em que escrevo, a versão mais recente do FreeType-2.10.1
, ela também funcione.
- Versão libpng
(1.6.34-1ubuntu0.18.04.2)
Renderizador de console
Crie um arquivo C ( main.c
no meu caso)
#include <stdio.h> int main() { printf("Hello, world\n"); return 0; }
$ clang -Wall -Werror -o main main.c $ ./main Hello, world
Conectamos bibliotecas do FreeType
Para procurar o caminho de inclusão (ou seja, diretórios que o compilador percorre ao procurar arquivos no
#include
) pelo FreeType, execute:
$ pkg-config --cflags freetype2 -I/usr/include/freetype2 -I/usr/include/libpng16
A linha
-I/usr/include/freetype2 -I/usr/include/libpng16
contém os sinalizadores de compilação necessários para ativar o FreeType no programa C.
#include <stdio.h> #include <freetype2/ft2build.h> #include FT_FREETYPE_H int main() { printf("Hello, world\n"); return 0; }
$ clang -I/usr/include/freetype2 \ -I/usr/include/libpng16 \ -Wall -Werror \ -o main \ main.c $ ./main Hello, world
Imprimimos a versão do FreeType
Dentro de
main()
inicialize o FreeType com
FT_Init_FreeType(&ft)
e verifique se há erros (as funções do FreeType retornam 0 se tiver êxito).
(A partir de agora, todas as funções que utilizarei são retiradas da
ajuda da API FreeType ).
FT_Library ft; FT_Error err = FT_Init_FreeType(&ft); if (err != 0) { printf("Failed to initialize FreeType\n"); exit(EXIT_FAILURE); }
Então, usando FT_Library_Version, obtemos o número da versão.
FT_Int major, minor, patch; FT_Library_Version(ft, &major, &minor, &patch); printf("FreeType's version is %d.%d.%d\n", major, minor, patch);
Se compilado usando o último comando, um erro de vinculador será exibido:
/tmp/main-d41304.o: In function `main': main.c:(.text+0x14): undefined reference to `FT_Init_FreeType' main.c:(.text+0x54): undefined reference to `FT_Library_Version' clang: error: linker command failed with exit code 1 (use -v to see invocation)
Para corrigir isso, adicione
-lfreetype
.
$ clang -I/usr/include/freetype2 \ -I/usr/include/libpng16 \ -Wall -Werror \ -o main \ -lfreetype \ main.c $ ./main FreeType's version is 2.8.1
Fonte Download
O primeiro passo para renderizar um caractere é baixar um arquivo de fonte. Estou usando o
ubuntu mono .
Para entender a diferença exata entre uma construção de face de fonte, uma família de fontes e fontes individuais, consulte a
documentação do
FreeType .
O terceiro argumento é chamado
índice de face . Ele foi projetado para permitir que os criadores de fontes insiram várias faces no mesmo tamanho de fonte. Como cada fonte tem pelo menos uma face, um valor 0 sempre funcionará, escolhendo a primeira opção.
FT_Face face; err = FT_New_Face(ft, "./UbuntuMono.ttf", 0, &face); if (err != 0) { printf("Failed to load face\n"); exit(EXIT_FAILURE); }
Definir tamanho do pixel para o rosto
Usando esta instrução, informamos ao FreeType a largura e a altura desejadas para os caracteres exibidos.
Se você passar zero para a largura, o FreeType interpretará isso como "o mesmo que os outros", neste caso 32px. Isso pode ser usado para exibir um caractere, por exemplo, com uma largura de 10 px e uma altura de 16 px.
Esta operação pode falhar em uma fonte de tamanho fixo, como no caso de emoji.
err = FT_Set_Pixel_Sizes(face, 0, 32); if (err != 0) { printf("Failed to set pixel size\n"); exit(EXIT_FAILURE); }
Obtendo índice para caracteres
Primeiro, volte para a
documentação do
FreeType e estabeleça uma convenção de nomenclatura. Um símbolo não é o mesmo que um
glifo . Um personagem é o que
char
diz, e um glifo é uma imagem que está de alguma forma associada a esse personagem. Essa relação é bastante complicada porque char pode corresponder a vários glifos: ou seja, acentos. Um glifo pode corresponder a muitos caracteres: ou seja, ligaduras, em que -> é representado como uma única imagem.
Para obter o índice de glifo correspondente ao caractere, usamos
FT_Get_Char_Index
. Como você pode entender, isso envolve caracteres e glifos correspondentes apenas um a um. Em um artigo futuro desta série, resolveremos o problema usando a biblioteca
HarfBuzz .
FT_UInt glyph_index = FT_Get_Char_Index(face, 'a');
Carregando um glifo do rosto
Tendo recebido o glyph_index, podemos carregar o glifo correspondente da nossa cara.
Em uma edição futura, discutiremos detalhadamente os vários sinalizadores de download e como eles permitem que você use recursos como fontes de dicas e bitmap.
FT_Int32 load_flags = FT_LOAD_DEFAULT; err = FT_Load_Glyph(face, glyph_index, load_flags); if (err != 0) { printf("Failed to load glyph\n"); exit(EXIT_FAILURE); }
Exibir um glifo em seu recipiente (slot de glifo)
Agora podemos finalmente exibir nosso glifo em seu contêiner (slot) especificado em
face->glyph
.
Também discutiremos sinalizadores de renderização no futuro, porque eles permitem o uso de renderização em LCD (ou sub-pixel) e antialiasing em escala de cinza.
FT_Int32 render_flags = FT_RENDER_MODE_NORMAL; err = FT_Render_Glyph(face->glyph, render_flags); if (err != 0) { printf("Failed to render the glyph\n"); exit(EXIT_FAILURE); }
Saída de caracteres para o console
O bitmap do glifo renderizado pode ser obtido em
face->glyph->bitmap.buffer
, onde é apresentado como uma matriz de valores de caracteres não assinados, portanto, seus valores variam de 0 a 255.
O buffer é retornado como uma matriz unidimensional, mas é uma imagem 2D. Para acessar a i-ésima linha da j-ésima coluna, calculamos a
column * row_width + row
, como em
bitmap.buffer[i * face->glyph->bitmap.pitch + j]
.
Você pode ver que, ao acessar a matriz, usamos
bitmap.width
em um loop e
bitmap.pitch
, porque o comprimento de cada linha de pixels é igual a
bitmap.width
, mas a “largura” do buffer é
bitmap.pitch
.
No código a seguir, todas as linhas e colunas são classificadas e caracteres diferentes são desenhados dependendo do brilho do pixel.
for (size_t i = 0; i < face->glyph->bitmap.rows; i++) { for (size_t j = 0; j < face->glyph->bitmap.width; j++) { unsigned char pixel_brightness = face->glyph->bitmap.buffer[i * face->glyph->bitmap.pitch + j]; if (pixel_brightness > 169) { printf("*"); } else if (pixel_brightness > 84) { printf("."); } else { printf(" "); } } printf("\n"); }
Saída do console.
$ clang -I/usr/include/freetype2 \ -I/usr/include/libpng16 \ -Wall -Werror \ -o main \ -lfreetype \ main.c && ./main FreeType's version is 2.8.1 .*****. .********. .********* . ***. *** *** .******** *********** .**. *** *** *** *** *** ***. *** .*********** *********** .*******..
→ O código completo pode ser visto
aquiConclusão
Criamos um renderizador de caracteres básico no console. Este exemplo pode (e será) estendido para renderizar caracteres em texturas OpenGL para oferecer suporte a emoji, renderização de subpixels, ligaduras e muito mais. Na próxima parte, falaremos sobre a suavização de subpixel de LCD em comparação com tons de cinza, seus prós e contras.
Até breve.