24 Objetos básicos del GDI:
La Fuente (Font)

Llegamos por fin a un objeto básico que se seguro que necesitamos en casi todos nuestros programas, y que estarías echando de menos: el texto.

Veremos en este capítulo que si bien, mostrar texto en pantalla es fácil, (basta con una función del API), el tema no es tan sencillo como pudiera pensarse, ya que Windows nos ofrece muchas posibilidades a la hora de trabajar con texto. Podemos elegir la forma, el tamaño, orientación, estilo, espaciado... de cada fuente. En este capítulo aprenderemos a mostrar texto y también a personalizarlo.

Mostrar un texto simple

El API siempre dispone de objetos de stock, de modo que si sólo queremos mostrar un texto en nuestra ventana, podemos usar la fuente por defecto para ello, sin complicarnos la vida.

La función más simple para mostrar texto es TextOut, que usa la fuente activa.

La forma de usarla es tan sencilla como indicar un manipulador de contexto de dispositivo, las coordenadas de inicio del texto, el propio texto, y el número de caracteres a mostrar. Por supuesto, seguiremos usando el mensaje WM_PAINT para actualizar la pantalla:

        case WM_PAINT:
           hDC = BeginPaint(hwnd, &ps);
           TextOut(hDC, 10, 10, "Hola, mundo!", 12);
           TextOut(hDC, 20, 30, "Curso WinAPI con Clase.", 23);
           EndPaint(hwnd, &ps);
           break;

Cambiar el color del texto

El texto mostrado por el ejemplo anterior usa la fuente del sistema, con el color por defecto: letras negras sobre fondo blanco.

Esto no resulta muy elegante, que digamos. Es bastante rudimentario y, desde luego, no es lo mejor que sabremos hacer.

Podemos cambiar el color del fondo, usando la función que ya conocemos SetBkColor, o podemos evitar el parche de color correspondiente al fondo, de modo que las letras se muestren sobre el contenido actual del fondo, sea del color que sea. Para lograr esto bastará con activar el modo transparente para el fondo, con la función que ya hemos usado: SetBkMode.

           SetBkColor(hDC, RGB(40,40,240));
           SetBkMode(hDC, TRANSPARENT);

El valor por defecto para el modo del fondo es ocaco, y para activarlo se usa el valor OPAQUE.

El siguiente paso es modificar el color del texto, para ello disponemos de otra función del API: SetTextColor, que precisa un manipulador de contexto de dispositivo y una referencia de color, para indicar el nuevo color del texto.

           SetTextColor(hDC, RGB(255,0,0));

Si necesitásemos averiguar el color actual del texto, podemos usar la función GetTextColor.

Ejemplo 22

El siguiente ejemplo demuestra el modo más simple de mostrar texto:

Nombre Fichero Fecha Tamaño Contador Descarga
Ejemplo 22 win022.zip 2004-04-25 2170 bytes 583

Crear fuentes personalizadas

El siguiente paso es aprender a usar cualquiera de las fuentes disponibles en el sistema, variando tanto el tipo, como el tamaño, orientación y estilo. Los parámetros que podemos cmabiar en una fuente son muchos, de modo que intentaremos explicar cada uno de ellos para que nos sea más sencillo adaptar las fuentes a nuestros gustos o necesidades.

Para ello disponemos de dos funciones: CreateFont y CreateFontIndirect. La primera precisa que le demos 14 parámetros que definen la fuente, la segunda crea la fuente a partir de los mismos parámetros, almacenados en una estructura LOGFONT.

En ambos casos, al igual que ocurre con las fuentes de stock, recibiremos un manipulador de fuente HFONT, que nos permitirá usar la fuente, ya sea para seleccionarla o eliminarla.

   HFONT fuente;
   HFONT grafico;
   LOGFONT lf= {80, 0, 450, 450, 300, FALSE, FALSE, FALSE,
              DEFAULT_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS,
              PROOF_QUALITY, DEFAULT_PITCH | FF_ROMAN, "Webdings"};

   fuente = CreateFont(-80, 0, 450, 450, 300, FALSE}, FALSE, FALSE,
      DEFAULT_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS,
      PROOF_QUALITY, DEFAULT_PITCH | FF_ROMAN, "Times New Roman");
   grafico = CreateFontIndirect(&lf);
   ...
   SelectObject(hDC, fuente);
   ...
   SelectObject(hDC, grafico);
   ...
   DeleteObject(fuente);
   DeleteObject(grafico);

Altura y anchura media de carácter

Existen algunos parámetros clave a la hora de medir una fuente, veamos algunos de ellos:

Línea de base es la línea sobre la que se apoyan las letras, en el caso de letras que sobresalen por debajo, la parte que apoya sobre esta línea es, generalmente, la del óvalo de la letra. La línea de base es lo que nos indica la inclinación de un texto.

Punto es la unidad de medida para fuentes. Un punto es, aproximadamente, 1/72 de pulgada, es decir, una fuente de 72 puntos de altura tendrá una pulgada de alto.

Celda es un rectángulo imaginario que contiene un carácter completo.

El tamaño de una fuente se define por dos parámetros: altura y anchura. Esto es evidente, ya que el texto tiene dos dimensiones. Sin embargo, aunque la altura es un parámetro claro, la anchura no lo es tanto. En algunas fuentes, los caracteres no tienen un ancho constante, sino proporcional a su forma, es decir, letras como la 'i' son más estrechas que otras, como la 'm'.

Estas fuentes, en la que cada carácter tiene una anchura diferente, son conocidas como de anchura proporcional; por contra, el otro tipo, en las que todos los caracteres tienen la misma anchura, se conoce como fuentes de anchura no proporcional:

Fuentes
Fuentes

Es por eso que cuando hablamos de anchura de fuentes nos referimos a anchura media de carácter.

Alturas de fuentes
Alturas de fuentes

Los valores de altura son igualmente imprecisos, ya que exiten caracteres de distintas alturas, por ejemplo, las letras como 'p', 'q' y 'g' sobresalen por la parte inferior, del mismo modo que las mayúsculas, o letras como la 't', 'l' y 'f' sobresalen por arriba. Normalmente se determina la altura como la distancia entre la parta inferior de la letra 'g' y la superior de la 'M'.

Es frecuente referirse también a la altura de la celda.

Cuando creamos fuentes, los valores de altura pueden ser positivos, negativos o cero. Los valores negativos indican medidas en función de la altura del carácter, y los positivos en función de la altura de la celda.

El valor nulo indica que se tome el valor por defecto para la altura.

En el caso de la anchura, el valor puede ser positivo o nulo. El valor positivo será el que se tome como anchura media, si es cero, se tomará el valor por defecto, que dependerá en cada caso del valor de altura elegido.

El ángulo de escape

Cada carácter puede tener un ángulo sobre la línea de base. Podemos, por ejemplo, inclinar los caracteres 90º y escribir una línea horizontal:

Ángulo de escape
Ángulo de escape

El ángulo de orientación

Se refiere al ángulo formado por la línea de base con el eje x del dispositivo. Cuando creamos fuentes tenemos una precisión de décimas de grados para precisar dicho ángulo, de modo que un valor de 900 indica 90º.

Peso

El peso indica cómo de gruesos son los trazos que se usan para mostrar el carácter, es lo que diferecia un carácter en negrita de uno normal. Tenemos mil valores posibles para el peso, los valores más bajos indican trazos finos, los más altos, trazos gruesos.

Como ayuda existen ciertas constantes predefinidas para asignar a este parámetro.

Cursiva

Se trata de un banderín, si se activa (valor TRUE), se generará una fuente cursiva, si se desactiva, una fuente vertical.

Subrayado

Otro banderín, si se activa se generará una fuente subrayada, en caso contrario, una normal.

Tachado

Un tercer banderín, que si se activa genera una fuente tachada.

Conjunto de caracteres

Permite elegir entre distintos conjuntos de caracteres. Recordemos que es ASCII no es el único conjunto existente, existen otros, y el API nos permite seleccionar algunos de ellos. Entre los más corrientes están, por ejemplo: el Windows, el Unicodetm y el de símbolos.

Precisión de salida

Básicamente, nos permite elegir una fuente de dispositivo, matricial o TrueType, si existen varias de distinto tipo y el mismo nombre.

Precisión de recorte

Afecta al tipo de rotación de caracteres cuando cambiamos la orientación de la fuente.

Calidad

Permite seleccionar, a la hora de mostrarla en pantalla, la calidad de la fuente.

Paso y familia

Sirve para indicar un tipo alternativo de fuente cuando la seleccionada no está disponible o no se especifica una fuente concreta.

Nombre

Nombre de la fuente seleccionada. Los ficheros de fuentes almacenan el aspecto visual de cada fuente. Esto nos da una enorme posibilidad a la hora de mostrar textos o símbolos.

Fuentes de stock

AL igual que con otros objetos del GDI que hemos usado antes, en el caso de las fuentes también disponemos de seis fuentes de stock, que podemos seleccionar mediante la función GetStockObject.

En concreto se trata de las siguientes:

Valor Significado
ANSI_FIXED_FONT Especifica una fuente de espacio no proporcional basada en el conjunto de caracteres de Windows. Normalmente se usa una fuente Courier.
ANSI_VAR_FONT Especifica una fuente de espacio proporcional basada en el conjunto de caracteres de Windows. Normalmente se usa una fuente MS Sans Serif.
DEVICE_DEFAULT_FONT Especifica la fuente por defecto para el dispositivo especificado. Se trata, típicamente, de la fuente System para dispositivos de visualización. Para algunas impresoras matriciales esta fuente es una que reside en la propia impresora. (Imprimir con esa fuente suele ser mucho más rápido que hacerlo con otra.).
OEM_FIXED_FONT Especifica una fuente de espacio no proporcional basada en un conjunto de caracteres OEM. Para ordenadores IBM® y compatibles, la fuente OEM está absada en el conjunto de caracteres del IBM PC.
SYSTEM_FONT Especifica la fuente System. Es una fuente de espacio proporcional basada en el conjunto de caracteres Windows, y se usa por el sistema operativo para mostrar los títulos de las ventanas, los nombres de menú y el texto en los cuadros de diálogo. La fuente System siempre está disponible. Otras fuentes sólo están disponibles si han sido instaladas.
SYSTEM_FIXED_FONT Especifica una fuente de espacio no proporcional compatible con la fuente System en versiones de Windows anteriores a la 3.0.
   HFONT fuente, anterior;

   fuente = GetStockObject(ANSI_FIXED_FONT);
   anterior = SelectObject(hDC, fuente);
   TextOut(hDC, 10, 10, "ANSI_FIXED_FONT", 15);

   SelectObject(hDC, anterior);
   DeleteObject(fuente);

Alineamientos de texto

Cuando mostramos un texto usando la función TextOut, ExtTextOut (que aún no hemos visto), indicamos unas coordenadas para situar el texto en el dispositivo. Estas coordenadas pueden referirse a diversos puntos dentro del texto. Podemos referirnos a la línea de base, al centro del texto, a la esquina superior izquierda, a la esquina superior derecha, etc.

En concreto, tenemos las siguientes opciones:

  • TA_BASELINE: El punto de referencia es la línea de base del texto.
  • TA_BOTTOM: El punto de referencia es el borde inferior del rectángulo externo que contiene el texto.
  • TA_TOP: El punto de referencia es el borde superior del rectángulo que contiene el texto.
  • TA_CENTER: El punto de referencia se alinea horizontalmente con el centro del rectángulo que contiene el texto.
  • TA_LEFT: El punto de referencia es el borde izquierdo del rectángulo que contiene el texto.
  • TA_RIGHT: El punto de referencia es el borde derecho del rectángulo que contiene el texto.
  • TA_NOUPDATECP: El valor del cursor no se actualiza después de mostrar el texto.
  • TA_UPDATECP: El valor del cursor se actualiza después de mostrar el texto.

Pero no sólo eso, estas opciones se pueden combinar, aunque no de cualquier modo. Sólo se pueden agrupar valores tomando uno o ninguno de cada uno de los grupos:

  • TA_LEFT, TA_RIGHT y TA_CENTER
  • TA_BOTTOM, TA_TOP y TA_BASELINE
  • TA_NOUPDATECP y TA_UPDATECP

Podemos, por ejemplo, combinar el valor TA_LEFT con TA_BASELINE o TA_CENTER con TA_TOP, etc. Para combinarlos se usa el operador de bits OR (|).

Para cambiar la alineación del texto se usa la función SetTextAlign, para obtener el valor actual se usa GetTextAlign.

Pero averiguar si uno de los bits está activo en el valor actual de alineamiento no es tan sencillo como pudiera parecer a primera vista. Los valores TA_xxx son bits dentro de un valor de alineamiento, y como hemos visto esos bits pueden estar combinados entre ellos.

Si queremos comprobar si el valor de alineamiento actual contiene el valor TA_LEFT no bastará con comparar el valor de retorno de GetTextAlign con TA_LEFT, ya que el valor actual puede tener también los valores TA_BOTTOM, TA_TOP, TA_BASELINE, TA_NOUPDATECP o TA_UPDATECP. Tampoco bastará con un AND de bits, ya que no sabemos si, por ejemplo, TA_CENTER equivale a TA_LEFT |TA_RIGHT. (Podría ser).

Los pasos a seguir son:

  1. Aplicar el operador de bits OR a los bits del grupo al que pertenece el valor que queremos verificar.
  2. Aplicar el operador de bits AND entre el resultado anterior y al valor retornado por GetTextAlign.
  3. Verificar la igualdad entre ese resultado y la bandera a verificar.

En nuestro ejemplo, tenemos:

  1. x = TA_LEFT | TA_RIGHT | TA_CENTER
  2. y = x & GetTextAlign(hDC);
  3. Verificar si y == TA_LEFT

Si la alineación actual es, por ejemplo, TA_RIGHT | TA_BASELINE, y queremos comprobar si contiene el valor TA_LEFT, la sentencia C puede ser como esta:

if(TA_LEFT == ((TA_LEFT | TA_RIGHT | TA_CENTER) & GetTextAlign(hDC)))...

Separación de caracteres

Normalmente, cuando mostramos texto en un dispositivo, la separación entre caracteres está predeterminada, y depende del diseño de la fuente. Pero, con el fin de hacer que el texto sea más ancho, podemos aumentar la separación entre caracteres, usando la función SetTextCharacterExtra.

Separación
Separación

Del mismo modo, podemos comprimir el texto usando valores de separación negativos.

Por último, podemos recuperar el valor de separación para un contexto de dispositivo determinado usando la función GetTextCharacterExtra.

Medidas de cadenas

A menudo nos interesa conocer las medidas que van a tener las cadenas en el dispositivo antes de mostrarlas, por ejemplo, para situarlas correctamente, separar las líneas adecuadamente, formatear párrafos, etc.

Disponemos de varias funciones en el API para esta tarea.

Empezaremos por la función GetTextMetrics, que nos proporciona datos sobre la fuente actual de un contexto de dispositivo en una estructura TEXTMETRIC.

Esta estructura nos informa principalmente sobre las medidas verticales de las líneas de texto, y nos da alguna información sobre medidas horizontales, aunque en este caso, algo menos precisas.

Medidas de cadenas
Medidas de cadenas

Justificar texto

El API también nos proporciona funciones para mostrar texto justificado: que se ajusta a los márgenes derecho e izquierdo del área de visualización.

Para ello deberemos usar dos funciones de forma conjunta. Por una parte GetTextExtentPoint32, que nos proporciona información sobre el tamaño de una línea de texto. Por otra, la función SetTextJustification, que prepara las cosas para que la siguiente función de salida de texto TextOut incluya la separación apropiada entre palabras.

La función GetTextExtentPoint32 sirve para calcular el espacio necesario de pantalla necesario para mostrar una cadena. Esta información la podemos usar, por una parte, para averiguar si cierta cadena cabe en el espacio en que queremos mostrarla, y por otra, para saber cuanto espacio sobra, si es que cabe.

El espacio sobrante se debe repartir entre las palabras de la cadena a justificar, eso se hace mediante la función SetTextJustification, que necesita como parámetros el manipulador del DC, el espacio extra, y el número de espacios entre palabras que contiene la cadena.

Hay que usar la función SetTextJustification con cuidado, ya que sus efectos permanecen hasta la siguiente llamada. Es decir, si usamos esta función para justificar una línea, y no anulamos su efecto, subsiguientes llamadas a GetTextExtentPoint32 podrán falsear la medida de la cadena, ya que se usará más espacio entre palabras para calcular la longitud de la cadena.

Para anular el efecto de la función se usa la misma función, pero con un valor nulo en el segundo parámetro, y también en el tercero, aunque en realidad ese parámetro se ignora si el segundo es nulo.

   char *cadena = "Para ello deberemos usar dos funciones de";
   SIZE tam;
   RECT re;

   GetClientRect(hwnd, &re);  // Obtener tamaño de la ventana
   SetTextJustification(hDC, 0, 0); // Anula cualquier justificación previa
   GetTextExtentPoint32(hDC, cadena, strlen(cadena), &tam); // Calcular tamaño de cadena
   SetTextJustification(hDC, re.right-tam.cx-20, espacios[i]); // Justificar
   TextOut(hDC, 10, 10, cadena, strlen(cadena));  // Mostrar cadena

Ejemplo 23

Nombre Fichero Fecha Tamaño Contador Descarga
Ejemplo 23 win023.zip 2004-04-25 5045 bytes 541