Representación de texto moderna en Linux: parte 1

Bienvenido a la primera parte de Modern Linux Text Rendering. En cada artículo de esta serie, desarrollaremos un programa C autónomo para visualizar un personaje o secuencia de personajes. Cada uno de estos programas implementará una función que considero necesaria para la representación moderna de texto.

En la primera parte configuraremos FreeType y escribiremos un renderizador de símbolos simple en la consola.



Esto es lo que escribiremos. Y aquí está el código.

Configuración del sistema


  • Mi sistema operativo: Ubuntu 18.04.2 LTS (bionic)
  • C compilador: clang version 6.0.0-1ubuntu2

Instalar FreeType


En Ubuntu, necesita instalar FreeType y libpng.

 $ sudo apt install libfreetype6 libfreetype6-dev $ sudo apt install libpng16-16 libpng-dev 

  • Tengo FreeType versión 2.8.1-2ubuntu2 , aunque en el momento de la escritura, la última versión de FreeType-2.10.1 , también funciona.
  • versión de libpng (1.6.34-1ubuntu0.18.04.2)

Renderizador de consola


Crear un archivo C ( main.c en mi caso)


 #include <stdio.h> int main() { printf("Hello, world\n"); return 0; } 

 $ clang -Wall -Werror -o main main.c $ ./main Hello, world 

Conectamos bibliotecas de FreeType


Para buscar la ruta de inclusión (es decir, directorios por los que pasa el compilador cuando busca archivos en #include ) para FreeType, ejecute:

 $ pkg-config --cflags freetype2 -I/usr/include/freetype2 -I/usr/include/libpng16 

La línea -I/usr/include/freetype2 -I/usr/include/libpng16 contiene los indicadores de compilación necesarios para habilitar FreeType en el 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 la versión de FreeType


Dentro de main() inicialice FreeType con FT_Init_FreeType(&ft) y verifique si hay errores (las funciones de FreeType devuelven 0 si tienen éxito).

(De ahora en adelante, todas las funciones que usaré se toman de la ayuda de la API FreeType ).

 FT_Library ft; FT_Error err = FT_Init_FreeType(&ft); if (err != 0) { printf("Failed to initialize FreeType\n"); exit(EXIT_FAILURE); } 

Luego, usando FT_Library_Version obtenemos el número de versión.

 FT_Int major, minor, patch; FT_Library_Version(ft, &major, &minor, &patch); printf("FreeType's version is %d.%d.%d\n", major, minor, patch); 

Si se compila utilizando el último comando, aparecerá un error de vinculador:

 /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 solucionar esto, agregue -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 

Descarga de fuente


El primer paso para representar un personaje es descargar un archivo de fuente. Estoy usando ubuntu mono .

Para comprender la diferencia exacta entre una construcción de fuente, una familia de fuentes y fuentes individuales, consulte la documentación de FreeType .

El tercer argumento se llama índice de la cara . Está diseñado para permitir a los creadores de fuentes insertar múltiples caras en el mismo tamaño de fuente. Como cada fuente tiene al menos una cara, siempre funcionará un valor de 0, eligiendo la primera opción.

  FT_Face face; err = FT_New_Face(ft, "./UbuntuMono.ttf", 0, &face); if (err != 0) { printf("Failed to load face\n"); exit(EXIT_FAILURE); } 

Establecer el tamaño de píxel para la cara


Usando esta instrucción, le decimos a FreeType el ancho y la altura deseados para los caracteres mostrados.

Si pasa cero para el ancho, FreeType interpreta esto como "lo mismo que los demás", en este caso 32px. Esto se puede usar para mostrar un carácter, por ejemplo, con un ancho de 10px y una altura de 16px.

Esta operación puede fallar en una fuente de tamaño fijo, como en el caso de los emoji.

 err = FT_Set_Pixel_Sizes(face, 0, 32); if (err != 0) { printf("Failed to set pixel size\n"); exit(EXIT_FAILURE); } 

Obtener índice para personaje


En primer lugar, regrese a la documentación de FreeType y establezca una convención de nomenclatura. Un símbolo no es lo mismo que un glifo . Un personaje es lo que dice char , y un glifo es una imagen que de alguna manera está asociada con ese personaje. Esta relación es bastante complicada porque el carácter puede corresponder a varios glifos: es decir, acentos. Un glifo puede corresponder a muchos caracteres: es decir, ligaduras, donde -> se representa como una sola imagen.

Para obtener el índice de glifos correspondiente al carácter, usamos FT_Get_Char_Index . Como puedes entender, esto implica emparejar caracteres y glifos solo uno a uno. En un artículo futuro de esta serie, resolveremos el problema usando la biblioteca HarfBuzz .

  FT_UInt glyph_index = FT_Get_Char_Index(face, 'a'); 

Cargando un glifo desde la cara


Habiendo recibido glyph_index, podemos cargar el glifo correspondiente de nuestra cara.

En una entrega futura, discutiremos en detalle los diversos indicadores de descarga y cómo le permiten usar características como sugerencias y fuentes de mapa de bits.

 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); } 

Mostrar un glifo en su contenedor (ranura de glifo)


Ahora finalmente podemos mostrar nuestro glifo en su contenedor (ranura) especificado en face->glyph .

También discutiremos las banderas de renderizado en el futuro, ya que permiten el uso de renderizado LCD (o subpíxel) y antialiasing en escala de grises.

 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); } 

Salida de caracteres a la consola


El mapa de bits del glifo representado se puede obtener de face->glyph->bitmap.buffer , donde se presenta como una matriz de valores de caracteres sin signo, por lo que sus valores oscilan entre 0 y 255.

El búfer se devuelve como una matriz unidimensional, pero es una imagen 2D. Para acceder a la fila i-ésima de la columna j-ésima, calculamos la column * row_width + row , como en bitmap.buffer[i * face->glyph->bitmap.pitch + j] .

Puede ver que al acceder a la matriz usamos bitmap.width en un bucle y bitmap.pitch , porque la longitud de cada línea de píxeles es igual a bitmap.width , pero el "ancho" del búfer es bitmap.pitch .

En el siguiente código, todas las filas y columnas se ordenan y se dibujan diferentes caracteres según el brillo del píxel.

 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"); } 

Salida de consola.

 $ clang -I/usr/include/freetype2 \ -I/usr/include/libpng16 \ -Wall -Werror \ -o main \ -lfreetype \ main.c && ./main FreeType's version is 2.8.1 .*****. .********. .********* . ***. *** *** .******** *********** .**. *** *** *** *** *** ***. *** .*********** *********** .*******.. 

→ El código completo se puede ver aquí

Conclusión


Hemos creado un renderizador de personajes básico en la consola. Este ejemplo se puede (y se ampliará) para representar caracteres en texturas OpenGL para admitir emoji, representación de subpíxeles, ligaduras y más. En la siguiente parte, hablaremos sobre el suavizado de subpíxeles LCD en comparación con los tonos de gris, sus ventajas y desventajas.

Hasta pronto.

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


All Articles