domingo, 5 de agosto de 2007

Dibujar o Imprimir múltiples líneas de texto

Dibujar texto en Java es muy simple a través de los métodos drawString de la clase Graphics. Ahora este método está pensado para dibujar o imprimir una sola línea de texto; si lo que deseamos es mostrar múltiples líneas el problema se complica ya que hay que calcular del texto el ancho de cada palabra para ir verificando hasta donde corresponde dibujar.

Si tratamos de dibujar (o imprimir) una cadena de texto muy larga con el método drawString obtendremos que el texto se "escapa" de la pantalla (o la hoja) de la siguiente manera:


Afortunadamente dentro de las clases estándar de Java hay una que realiza este trabajo por nosotros: "LineBreakMeasurer" (dentro del paquete java.awt.font).

Básicamente lo que hace esta clase es verificar cuando el texto cumple con cierto ancho especificado (a través de un FontRenderContext) y va generando objetos TextLayout que permiten imprimir las distintas líneas de texto deseadas. Entonces así se puede lograr que se dibujen las múltiples líneas de la siguiente manera:



Vamos a definir ahora un método que dibuja múltiples líneas de un String en un Graphics dado:

/**
* Dibuja/imprime el texto indicado en múltiples líneas.
*
* @param graphics2D objeto Graphics2D en el cual se dibujará el texto
* @param posComienzoTexto posición inicial donde se comenzará a dibujar
* @param anchoMaximo ancho máximo a escribir el texto antes de pasar a
* una nueva línea
* @param texto cadena de texto a escribir
* @return
*/
public static Point2D imprimirMuchasLineas(Graphics2D graphics2D,
Point2D posComienzoTexto, float anchoMaximo, String texto) {

AttributedString as = new AttributedString(texto);
FontRenderContext frc = graphics2D.getFontRenderContext();

LineBreakMeasurer medidor = new LineBreakMeasurer(
as.getIterator(), frc);
while (true) {
TextLayout layout = medidor.nextLayout(anchoMaximo);
if (layout == null) break;
posComienzoTexto.setLocation(posComienzoTexto.getX(),
posComienzoTexto.getY()+layout.getAscent());
float dx = 0;
layout.draw(graphics2D,
(float)posComienzoTexto.getX() + dx,
(float)posComienzoTexto.getY());
posComienzoTexto.setLocation(posComienzoTexto.getX(),
posComienzoTexto.getY()+
layout.getDescent()+
layout.getLeading());
}

return posComienzoTexto;
}

A partir de este método se podría escribir cualquier cadena de texto, tanto en pantalla como en impresora, sin importar el largo de la misma.

Ahora hay que tener en cuenta que además del problema de dibujar el texto en varias líneas, también es importante recordar que ningún método de dibujo va a tener en cuenta los caracteres de fin de línea que pueda contener un String. Por esto es importante saber que si deseo que se tengan en cuenta dichos caracteres tengo que detectarlos explícitamente, por ej. a través de un StringTokenizer.

A continuación transcribo unas líneas de código que muestran cómo se pueden detectar las múltiples líneas que pueda contener un String (separados por caracteres de fin de línea "\n") y dibujarlas teniendo en cuenta que entren en un ancho determinado dentro de la pantalla o una hoja impresa a partir del método definido más arriba.

Point2D posComienzoTexto = new Point2D.Float(100,100);
float anchoMaximo = 400;

StringTokenizer st = new StringTokenizer(cadena, "\n\r\f");
while(st.hasMoreTokens()) {
String c = st.nextToken();
posComienzoTexto = imprimirMuchasLineas(g2d,
posComienzoTexto, anchoMaximo, c);
}

En este ejemplo la variable posComienzoTexto indica la posición en donde se comenzará a escribir el texto dentro del Graphics correspondiente, siendo la coordenada "y" del punto la parte superior de la primer línea de texto que se va a dibujar.
La variable anchoMaximo representa el ancho máximo que pueda tener una línea de texto, en coordenadas del dispositivo que representa el objeto Graphics.
A continuación se puede ver un dibujo que muestra gráficamente como estos parámetros afectan a la forma en que se dibujará el texto en pantalla o impresora:




Cabe aclarar que la clase LineBreakMeasurer sólo tiene en cuenta cuando una palabra completa no entra en la línea determinada por el ancho indicado, y no permite calcular la posibilidad de separar las palabras con guiones o justificando su ancho en toda la línea. Para lograr separar las líneas de estas otras formas se requerirá de una programación un poco más compleja que no viene al caso comentar en este artículo.