Renderização de texto moderno no Linux: parte 1

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 aqui

Conclusã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.

Source: https://habr.com/ru/post/pt461497/


All Articles