Latest article: Exporter des données depuis Excel

SFML et l’Unicode

Un tutoriel sur la gestion d’Unicode et des caractères spéciaux avec la bibliothèque graphique SFML

SFML est un outil vraiment merveilleux. A se demander comment on a fait pour vivre sans, je me vois mal maintenant revenir vers la vieille SDL.

Habituellement j’utilise SFML pour le fenêtrage et la gestion des évènements, pour l’affichage j’utilise directement OpenGL (3D oblige).

Mais j’ai décidé de déléguer la gestion et l’affichage du texte à SFML pour deux raisons : TrueType et Unicode.

Si charger une font TrueType est simple et bien décrit dans les tutoriaux, afficher correctement les caractères Unicode est une autre histoire…

Pré-requis

Tout d’abord vous allez avoir besoin d’une font qui gère l’Unicode et propose tous les caractères nécessaires. Mon but était d’afficher les caractères japonnais, j’ai donc utilisé Arial Unicode MS, également utilisée par Firefox.

Font.LoadFromFile ( "arial_unicode.ttf" , 14 );
sf::String sftext ( text , Font , 14 );
Window.Draw ( sftext );

Vous êtes surement familiers avec ce code, il se contente d’afficher le texte text avec la font arial_unicode.ttf de taille 14.

Attention toutefois, text ne doit pas être une std::string si vous comptez utiliser Unicode.

std::string est une chaîne de caractères de type char, de l’ASCII donc. Utilisez donc son équivalent UTF-8 : std::wstring (pour “wide string”).

std::wstring text = L"du texte !";

std::wstring s’utilise comme std::string, à la différence du L qui précède les chaines. Ce L sert à spécifier que la chaîne qui suit utilise l’encodage UTF-8 au lieu de l’ISO-8859-1.

Un exemple avec du texte japonais mélangé à du français (je tire ça d’un dico français/japonais) :

std::wstring text = L"Texte Japonais : 月 【つき ・ tsuki 】 lune";

Si vous utilisez cette chaîne de caractère avec le code précédent, vous verrez que seuls les caractères de l’alphabet latins s’affichent. C’est parce qu’il faut spécifier un charset.

Le charset

Le charset est un tableau d’unsigned int qui spécifie à SFML quels caractères il doit générer à partir de la font. En effet, pour pouvoir afficher du texte, il faut d’abord transformer les caractères en bitmaps. Et si tous les caractères Unicode devaient être rendus en bitmap, non seulement le temps de chargement serait long, mais la mémoire utilisée serait colossale (plus de 160Mo avec Arial Unicode MS de taille 24 !).

SFML nous laisse donc spécifier les caractères que l’ont veut générer, et par défaut ce sont les 256 premiers caractères (qui correspondent aux caractères ASCII et quelques étendus).

Les caractères sont identifiés par leur code (un unsigned int donc), et vous pouvez trouver les codes et leurs équivalents sur le site de l’Unicode : http://unicode.org/charts/.

Chose à savoir, quand vous spécifiez le charset à SFML, le premier code spécifié doit être 0x20, sinon rien ne s’affichera.

Généralement les codes sont organisés en plages, par exemple : de 0x20 à 0xFF pour les caractères Latins.

Voici donc un petit snippet permettant de spécifier un charset à SFML à partir d’une liste de plages :

// ===========================================================================================
/// A range of UTF-8 codes used to generate the final font charset
// ===========================================================================================
struct CharsetRange
{
	/// The minimum UTF-8 code
	unsigned int Min;

	/// The maximum UTF-8 code
	unsigned int Max;

	/// Get the range between the two UTF-8 codes
	unsigned int GetRange () const { return Max - Min + 1; }

	/// Constructor
	CharsetRange ( unsigned int min , unsigned int max ) : Min ( min ) , Max ( max ){}
};

Cette structure représente une plage de codes consécutifs.

// ===========================================================================================
/// A charset is a set of UTF-8 codes organized as ranges
// ===========================================================================================
typedef std::vector<CharsetRange> Charset;

Et ceci est notre charset : une liste de plages.

Maintenant on va récupérer cette liste de plages pour générer un tableau d’unsigned int que l’on spécifiera à SFML lors de la création de la font.

// ===========================================================================================
/// Set the current font
// ===========================================================================================
void SetFont ( const std::string& filename , unsigned int size , const Charset& ranges )
{

	// Determine the number of characters in the charset
	unsigned int totalRange = 0;
	if ( ranges.size() > 0 )
	{
		for ( std::size_t i = 0; i < ranges.size(); ++i )
			totalRange += ranges[i].GetRange();
	}
	else
		totalRange = 0xFF - 0x20;

	sf::Uint32 charset[totalRange];

	// Add the characters to the charset
	int it = 0;
	if ( ranges.size() > 0 )
	{
		for ( std::size_t i = 0; i < ranges.size(); ++i )
		{
			for ( unsigned int character = ranges[i].Min; character <= ranges[i].Max; ++character )
			{
				charset[it++] = character;
			}
		}
	}
	else
	{
		for ( unsigned int character = 0x20; character <= 0xFF; ++character )
		{
			charset[it++] = character;
		}
	}

	// Load the font
	if ( !Font.LoadFromFile ( filename , size , charset ) )
	{
		Font = sf::Font::GetDefaultFont();
	}
}

Et voilà !

Vous remarquerez qu’il y a une vérification : si le vector est vide, on spécifie une plage par défaut.

Et pour ceux qui voudraient afficher des caractères japonais et qui ont du mal à trouver les plages correspondantes aux différents alphabets, les voici :

// ===========================================================================================
/// A list of default charsets
// ===========================================================================================
struct DefaultCharsets
{

	// ===========================================================================================
	/// Return the default charset for european and american characters
	// ===========================================================================================
	static Charset GetDefault()
	{
		Charset ranges;
		ranges.push_back ( CharsetRange ( 0x20 , 0xFF ) );		// ANSI
		return ranges;
	}

	// ===========================================================================================
	/// Return the japanese charset
	// ===========================================================================================
	static Charset GetJapanese()
	{
		Charset ranges;
		ranges.push_back ( CharsetRange ( 0x20 , 0xFF ) );	// ANSI
		ranges.push_back ( CharsetRange ( 0x3000 , 0x30FF ) );	// Hiragana
		ranges.push_back ( CharsetRange ( 0x30A0 , 0x30FF ) );	// Katakana
		ranges.push_back ( CharsetRange ( 0x31F0 , 0x31FF ) );	// Ainu
		ranges.push_back ( CharsetRange ( 0x4E00 , 0x9FAF ) );	// Kanji
		ranges.push_back ( CharsetRange ( 0xFF65 , 0xFFFF ) );	// Halfwidth katakana
		return ranges;
	}
};

Je dois avouer avoir eu du mal à toutes les trouver…

C’est quand même assez incroyable à quel point les choses peuvent être simples quand on dispose de bons outils comme SFML !

Mentions

    Discussion

    1. Pouet

      April 15, 2011
      3:14 pm

      Merci beaucoup, c’est exactement ce qu’il me fallait, et ça marche nickel (:

      Reply

    Reply to Pouet (cancel)