Programmation Java Swing/Dessin de texte et effets

Le dessin d'un composant est réalisé par les méthodes paint de la classe du composant (en Swing, la méthode void paintComponent(Graphics g)) utilisant un contexte graphique.

Dessin de texte

modifier

Les méthodes de tracé de texte utilisent la couleur et la police de caractères courantes du contexte graphique. Les coordonnées spécifiées sont celles du point du début de ligne (à gauche pour le sens d'écriture de gauche à droite, ou à droite pour l'autre sens) situé au niveau de la ligne de base des caractères.

Tracer une chaîne de caractères
g.drawString(String, int x, int y)
g.drawString(String, float x, float y)
g.drawString(AttributedCharacterIterator, int x, int y)
g.drawString(AttributedCharacterIterator, float x, float y)
La méthode drawString dessine la chaîne de caractère qu'elle soit spécifiée sous la forme d'une chaîne java.lang.String, d'un séquence de caractères avec attributs java.text.AttributedCharacterIterator. La classe java.awt.Graphics2D surcharge les méthodes pour autoriser des coordonnées sous forme de nombres à virgule flottante.
Tracer un tableau de caractères
g.drawChars(char[], int offset, int length, int x, int y)
La méthode drawChars dessine la chaîne de caractère représenté par le tableau de caractères spécifié.
Tracer un tableau d'octets
g.drawBytes(byte[], int offset, int length, int x, int y)
La méthode drawChars dessine la chaîne de caractère représenté par le tableau de caractères spécifié.

Police de caractères

modifier

La police de caractères utilisée est celle définie par la méthode void setFont(Font f) du contexte graphique. Elle est initialisée avec celle assignée au composant.

Gras, italique, soulignement et barré

modifier

Les styles gras et italiques sont gérés par la classe java.awt.Font (constantes PLAIN, BOLD, ITALIC, BOLD_ITALIC).

Le soulignement doit être géré par le code de dessin après ou avant avoir tracé le texte. La classe java.awt.Font fournit des informations pour le soulignement (distance et épaisseur de trait) via une instance de la classe java.awt.font.LineMetrics. Le code ci-dessous affiche un texte souligné, en exploitant ces informations et en utilisant l'objet java.awt.FontMetrics pour mesurer le texte et obtenir la longueur de la ligne de soulignement du texte.

String texte_souligne = "Voici comment souligner le texte."
int tx = 10, ty = 20;

g.drawString(texte_souligne, tx, ty);

FontMetrics fm = g.getFontMetrics();
int tw = fm.stringWidth(texte_souligne);

LineMetrics lm = g.getFont().getLineMetrics(texte_souligne, g.getFontRenderContext());
float line_offset = lm.getUnderlineOffset();
float line_thickness = lm.getUnderlineThickness();

int ly = (int)(ty+line_offset + line_thickness /2); // Position
g.setStroke(new BasicStroke(line_thickness)); // Épaisseur
g.drawLine(tx, ly, tx+tw, ly);

Le style barré utilise le même principe en exploitant les informations retournées par les méthodes getStrikethroughOffset() et getStrikethroughThickness(). Dans le code précédent, il suffit de remplacer les valeurs affectées aux variables line_offset et line_thickness :

float line_offset = lm.getStrikethroughOffset();
float line_thickness = lm.getStrikethroughThickness();

Les styles souligné et barré ne sont pas gérés directement par la classe java.awt.Font car il s'agit d'éléments graphiques (lignes) séparés du texte, pouvant avoir une couleur différente du texte, ou une épaisseur différente de celle indiquée par la classe java.awt.font.LineMetrics.

Exemple : Texte en dégradé de couleurs

modifier

La méthode de remplissage est représentée par une instance de la classe java.awt.Paint. L'instance par défaut remplit les formes avec une couleur de manière unie. Il existe d'autres classes de remplissage dont celles permettant de peindre une forme en dégradé de couleurs.

Le tracé de texte est en fait un remplissage et il est dont possible d'utiliser un dégradé de couleurs.

 
Texte affiché en dégradé de couleurs.

Le code ci-dessous affiche une ligne de texte en dégradé de couleurs (voir image ci-dessus).

package org.wikibooks.fr.test;

import java.awt.*;
import java.util.*;

import javax.swing.*;

/**
 * Test affichage en dégradé d'un texte.
 * @author fr.wikibooks.org
 */
public class TestPaint extends JComponent
{
	public static void main(String[] args)
	{
		JFrame f = new JFrame("Test de rendu avec Java Swing");
		f.setSize(800, 300);
		f.add(new TestPaint());
		f.setVisible(true);
	}

	/** Police de caractères utilisée. */
	private static final Font F_TEXTE = new Font("Dialog", Font.BOLD, 80);

	/** Couleurs utilisées. */
	private static final Color
		C_BG = Color.BLACK,
// Orange -> Rouge
		C_FG_TEXT_1 = new Color(220,190,0),
		C_FG_TEXT_2 = new Color(250,50,0);
// Bleu -> Vert
//		C_FG_TEXT_1 = new Color(40,120,240),
//		C_FG_TEXT_2 = new Color(80,210,170);

	/** Options pour améliorer l'affichage. */
	private HashMap<RenderingHints.Key, Object> hm_hints = new HashMap<RenderingHints.Key, Object>();

	private TestPaint()
	{
		hm_hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		hm_hints.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
		hm_hints.put(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
		hm_hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		hm_hints.put(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE);
		hm_hints.put(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
		setBackground(C_BG);
	}

	@Override
	protected void paintComponent(Graphics gg)
	{
		Graphics2D g = (Graphics2D) gg;
		g.setRenderingHints(hm_hints);

		// Selon les dimensions du composant
		Dimension d = getSize();
		g.setColor(getBackground());
		g.fillRect(0, 0, d.width, d.height);

		g.setFont(F_TEXTE);
		FontMetrics fm = g.getFontMetrics();
		// Hauteurs des caractères (au dessus et en dessous de la ligne de base)
		int ha = fm.getMaxAscent(), hb = fm.getMaxDescent();

		// Texte à afficher :
		String s = "fr.wikibooks.org";
		// Largeur du texte, et calcul de la position centrée du texte
		int ws = fm.stringWidth(s),
			x = (d.width-ws)/2, y = (d.height+ha-hb)/2;

		// Dégradé linéaire vertical débutant à 5 pixels du haut et se terminant à 5 pixels du bas du texte.
		GradientPaint gp = new GradientPaint(new Point(x,y-ha+5), C_FG_TEXT_1, new Point(x,y+hb-5), C_FG_TEXT_2);
		Paint p = g.getPaint(); // Pour restaurer
		g.setPaint(gp);
		g.drawString(s, x, y);
		g.setPaint(p); // Restauration
	}
}

Utiliser la forme des caractères

modifier

Pour un affichage de texte plus évolué, il est possible d'exploiter la forme des caractères. La forme des caractères est représentée par une instance de la classe java.awt.font.GlyphVector. Cette classe possède des méthodes retournant des instances de la classe java.awt.Shape qui peuvent donc être manipulées, et notamment utilisées avec les méthodes draw et fill du contexte graphique.

 
Contour d'un texte et effets utilisant la forme des caractères.

Le texte tel que montré par l'image ci-dessus est dessiné par le code ci-dessous et reprend la première partie du code de la section précédente, en remplaçant l'affichage du texte par la récupération de la forme des caractères. Celle-ci est obtenue en appelant la méthode createGlyphVector de la police de caractère, en lui passant le contexte de rendu et le texte.

Cette forme des caractères est ensuite utilisée :

  • pour obtenir les rectangles autour du caractère w (en position 3),
  • pour tracer le contour des glyphes du texte en pointillé.
package org.wikibooks.fr.test;

import java.awt.*;
import java.awt.font.*;
import java.awt.geom.*;
import java.util.*;

import javax.swing.*;

/**
 * Test affichage en dégradé d'un texte.
 * @author fr.wikibooks.org
 */
public class TestPaint extends JComponent
{
	public static void main(String[] args)
	{
		JFrame f = new JFrame("Test de rendu avec Java Swing");
		f.setSize(800, 300);
		f.add(new TestPaint());
		f.setVisible(true);
	}

	/** Police de caractères utilisée. */
	private static final Font F_TEXTE = new Font("Dialog", Font.BOLD, 80);

	/** Couleurs utilisées. */
	private static final Color
		C_BG = Color.BLACK,
		C_LIGNE = new Color(210,160,40),
		C_RECT_1 = new Color(20,120,240),
		C_RECT_2 = new Color(0,64,128),
		C_TEXTE = Color.WHITE;

	/** Options pour améliorer l'affichage. */
	private HashMap<RenderingHints.Key, Object> hm_hints = new HashMap<RenderingHints.Key, Object>();

	private TestPaint()
	{
		hm_hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		hm_hints.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
		hm_hints.put(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
		hm_hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		hm_hints.put(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE);
		hm_hints.put(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
		setBackground(C_BG);
	}

	@Override
	protected void paintComponent(Graphics gg)
	{
		Graphics2D g = (Graphics2D) gg;
		g.setRenderingHints(hm_hints);

		// Selon les dimensions du composant
		Dimension d = getSize();
		g.setColor(getBackground());
		g.fillRect(0, 0, d.width, d.height);

		g.setFont(F_TEXTE);
		FontMetrics fm = g.getFontMetrics();
		// Hauteurs des caractères (au dessus et en dessous de la ligne de base)
		int ha = fm.getMaxAscent(), hb = fm.getMaxDescent();

		// Texte à afficher :
		String s = "fr.wikibooks.org";
		// Largeur du texte, et calcul de la position centrée du texte
		int ws = fm.stringWidth(s),
			x = (d.width-ws)/2, y = (d.height+ha-hb)/2;

		// Ensemble de glyphes du texte à partir de la police de caractères
		GlyphVector gv = F_TEXTE.createGlyphVector(g.getFontRenderContext(), s);
		Shape
			sh_title = gv.getOutline(), // Contour des caractères
			// Caractère en position 3 (w) :
			sh_w_ext = gv.getGlyphLogicalBounds(3), // - Rectangle de toute la zone du caractère
			sh_w     = gv.getGlyphVisualBounds(3);  // - Rectangle resserré sur le caractère

		g.setColor(C_LIGNE);
		g.fillRect(x-100, y-14, ws+200, 16);

		AffineTransform at = g.getTransform();
		g.translate(x, y);

		g.setColor(C_RECT_1);
		g.fill(sh_w_ext);
		g.setColor(C_RECT_2);
		g.fill(sh_w);

		// Tracé en pointillé, épaisseur 3 pixels
		Stroke sk = new BasicStroke(3, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 5.0f, new float[]{ 10f, 5f }, 5f);
		Stroke st = g.getStroke();
		g.setStroke(sk);
		g.setColor(C_TEXTE);
		g.draw(sh_title);
		g.setStroke(st);    // Restaurer
		g.setTransform(at); // Restaurer

		// Équivalent de g.drawString :
		//g.drawGlyphVector(gv, x, y);
	}
}