Bug 10319 - Java does not display correctly some glyphs
Summary: Java does not display correctly some glyphs
Status: NEW
Alias: None
Product: DejaVu
Classification: Unclassified
Component: Sans (show other bugs)
Version: unspecified
Hardware: Other All
: medium normal
Assignee: Deja Vu bugs
QA Contact:
Keywords: notourbug
Depends on:
Reported: 2007-03-16 09:48 UTC by Mathieu Legris
Modified: 2007-04-11 15:22 UTC (History)
0 users

See Also:
i915 platform:
i915 features:

Source code of a program that generates font texture (16.44 KB, text/x-java)
2007-03-17 03:13 UTC, Mathieu Legris
Output Image showing incorrectly placed glyphs (217.80 KB, image/png)
2007-03-17 03:16 UTC, Mathieu Legris
Package with source code, font and jar file (466.58 KB, application/x-zip-compressed)
2007-04-11 15:19 UTC, Mathieu Legris

Description Mathieu Legris 2007-03-16 09:48:38 UTC

I have written a program in Java to build font textures (for a video game).
Java, with J2SE 6, does not return correct informations about each glyph.
My program reported some glyphs as invalid (this means that glyph width, height or advance are below zero or greater than the maximum specified in the font). Some glyphs are not reported as invalid but are displayed at the wrong position. I can send a PNG image of the generated texture if necessary.

DejaVu is the only font that does not work with my program. The bug comes probably from the font manager in Java or it is just because Java does not support OpenType. But it would be great if someone could fix this.

Here is the debug output for the font DejaVuSansLGC regular with size 20:

Requested characters: 3089
Building: data/fonts/DejaVuLGC_Sans-regular.ttf
Family: DejaVu LGC Sans; Face name: DejaVu LGC Sans; Logical name: DejaVu LGC Sans; Size: 20
Num glyphs: 3498
Invalid character advance: codePoint=258; advance=-21506
Invalid character advance: codePoint=260; advance=-16387
Invalid character advance: codePoint=261; advance=17386
Invalid character advance: codePoint=265; advance=1024
Invalid character advance: codePoint=274; advance=-22639
Invalid character advance: codePoint=276; advance=4100
Invalid character advance: codePoint=278; advance=-7173
Invalid character advance: codePoint=288; advance=-29694
Invalid character advance: codePoint=290; advance=-29694
Invalid character advance: codePoint=293; advance=12286
Invalid character advance: codePoint=296; advance=-27647
Invalid character advance: codePoint=298; advance=-22528
Invalid character advance: codePoint=299; advance=-22528
Invalid character advance: codePoint=300; advance=-31744
Invalid character advance: codePoint=301; advance=-31744
Invalid character advance: codePoint=308; advance=-27648
Invalid character advance: codePoint=309; advance=482
Invalid character advance: codePoint=310; advance=-1023
Invalid character advance: codePoint=311; advance=-1023
Invalid character advance: codePoint=313; advance=15383
Invalid character advance: codePoint=314; advance=1021
Invalid character advance: codePoint=315; advance=15383
Invalid character advance: codePoint=317; advance=15382
Invalid character advance: codePoint=319; advance=8192
Invalid character advance: codePoint=324; advance=-1015
Invalid character advance: codePoint=325; advance=11255
Invalid character advance: codePoint=326; advance=-1015
Invalid character advance: codePoint=340; advance=-123
Invalid character advance: codePoint=342; advance=-123
Invalid character advance: codePoint=348; advance=11248
Invalid character advance: codePoint=349; advance=11248
Invalid character advance: codePoint=354; advance=22526
Invalid character advance: codePoint=362; advance=-3
Invalid character advance: codePoint=363; advance=-481
Invalid character advance: codePoint=365; advance=-27647
Invalid character advance: codePoint=368; advance=-30716
Invalid character advance: codePoint=369; advance=-7711
Invalid character width: codePoint=372; width=243
Invalid character width: codePoint=373; width=243
Invalid character advance: codePoint=374; advance=-3
Invalid character width: codePoint=375; width=24073
Invalid character advance: codePoint=483; advance=2048
Invalid character advance: codePoint=488; advance=24565
Invalid character advance: codePoint=490; advance=-5592
Invalid character advance: codePoint=491; advance=-5592
Invalid character advance: codePoint=494; advance=-24367
Invalid character advance: codePoint=500; advance=-7718
Invalid character advance: codePoint=504; advance=6174
Invalid character advance: codePoint=513; advance=32222
Invalid character advance: codePoint=515; advance=2048
Invalid character advance: codePoint=517; advance=-12551
Invalid character advance: codePoint=519; advance=1023
Invalid character advance: codePoint=523; advance=4099
Invalid character advance: codePoint=527; advance=29696
Invalid character advance: codePoint=529; advance=-8173
Invalid character advance: codePoint=535; advance=-123
Invalid character advance: codePoint=538; advance=482
Invalid character advance: codePoint=539; advance=-9217
Invalid character advance: codePoint=542; advance=21366
Invalid character advance: codePoint=543; advance=15227
Invalid character advance: codePoint=563; advance=-24102
Invalid character advance: codePoint=912; advance=12292
Invalid character advance: codePoint=939; advance=-28094
Invalid character advance: codePoint=1143; advance=-15357
Invalid character height: codePoint=1161; height=27
Invalid character advance: codePoint=1242; advance=-7747
Invalid character advance: codePoint=1246; advance=14335
Invalid character advance: codePoint=1247; advance=14335
Invalid character advance: codePoint=1262; advance=-3
Invalid character advance: codePoint=1263; advance=8198
Invalid character advance: codePoint=1264; advance=-25053
Invalid character advance: codePoint=1266; advance=-25053
Invalid character advance: codePoint=1269; advance=-25053
Invalid character advance: codePoint=7690; advance=5124
Invalid character advance: codePoint=7691; advance=-8176
Invalid character advance: codePoint=7692; advance=5124
Invalid character advance: codePoint=7698; advance=6114
Invalid character advance: codePoint=7699; advance=-7711
Invalid character advance: codePoint=7701; advance=-7711
Invalid character advance: codePoint=7714; advance=-7173
Invalid character advance: codePoint=7716; advance=-7173
Invalid character advance: codePoint=7719; advance=-7711
Invalid character advance: codePoint=7728; advance=-28661
Invalid character advance: codePoint=7729; advance=-28661
Invalid character advance: codePoint=7736; advance=13537
Invalid character advance: codePoint=7740; advance=6148
Invalid character advance: codePoint=7748; advance=13537
Invalid character advance: codePoint=7749; advance=482
Invalid character advance: codePoint=7780; advance=7864
Invalid character advance: codePoint=7782; advance=-27646
Invalid character advance: codePoint=7786; advance=7712
Invalid character advance: codePoint=7793; advance=15227
Invalid character advance: codePoint=7795; advance=-7711
Invalid character advance: codePoint=7808; advance=-7711
Invalid character advance: codePoint=7809; advance=-7711
Invalid character advance: codePoint=7810; advance=-7711
Invalid character advance: codePoint=7811; advance=-7711
Invalid character advance: codePoint=7812; advance=19464
Invalid character advance: codePoint=7813; advance=19464
Invalid character advance: codePoint=7814; advance=-7711
Invalid character advance: codePoint=7818; advance=-5493
Invalid character advance: codePoint=7840; advance=-17275
Invalid character advance: codePoint=7841; advance=-1983
Invalid character advance: codePoint=7852; advance=-3
Invalid character advance: codePoint=7862; advance=54
Invalid character advance: codePoint=7868; advance=-1983
Invalid character advance: codePoint=7898; advance=4097
Invalid character advance: codePoint=7899; advance=4097
Invalid character advance: codePoint=7900; advance=4097
Invalid character advance: codePoint=7901; advance=4097
Invalid character advance: codePoint=7905; advance=-10182
Invalid character advance: codePoint=7912; advance=-3
Invalid character advance: codePoint=7914; advance=-3
Invalid character advance: codePoint=7918; advance=-3
Invalid character advance: codePoint=7919; advance=-3
Invalid character advance: codePoint=7928; advance=1019
Invalid character advance: codePoint=8129; advance=7160
Invalid character height: codePoint=8272; height=25
Undisplayable: 81; Invalid: 118
Total texture height: 858

Thank you for reading this message.
Comment 1 Denis Jacquerye 2007-03-16 09:58:09 UTC
Thank you Mathieu.

I'm a bit confused with the advance, height or width given. They sound really wrong. What unit are they in?
Comment 2 Mathieu Legris 2007-03-17 01:59:28 UTC
(In reply to comment #1)
> Thank you Mathieu.
> I'm a bit confused with the advance, height or width given. They sound really
> wrong. What unit are they in?

All units are in pixels.

Here is the source code of a modified program:

package fonttest;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

public class FontTextureTest {
	public static final int MAX_FONT_STYLES=	256;
	public static final int MAX_TEXTURES=		256;

	public static final int PIXEL_A=0, PIXEL_RGBA=1;
	public static final int
		STYLE_BOLD=			1,
    public static final String STYLE_STRING[]={ "regular", "bold", "italic", "bold-italic" };
	public static int
		TEXTURE_SINGLE				=0, // All characters of all styles on a single texture.
		TEXTURE_SINGLE_PER_STYLE	=1, // All characters of a style on a single texture.
		TEXTURE_NEW_PER_STYLE		=2, // Each style has its own set of textures.
		TEXTURE_SHARED				=3; // All styles shared the textures.
	public static final int HORIZONTAL_SPACING=1;
	public static final int VERTICAL_SPACING=1;
	public static class Parameters {
		public String fontName;
		public int size;
		public int blocks[][];
		public int textureWidth, textureHeight;
		public int textureOption;
		public boolean antialiasFlag;
	public static class Style {
		public int ascent, descent, leading, height;
		public int maxAscent, maxDescent, maxAdvance;
		public int maxWidth, maxHeight;
		public int nbCharacters;
		public Glyph characters[];		
	public static class Glyph {
		public int codePoint;
		public short tx, ty, tw, th; // Coordinates in texture.
		public short x, y; // Reference point.
		public short advance;
		public short texture;
	public static class Texture {
		public int width, height;
		public BufferedImage data;
//		public byte data[];
	public boolean debugFlag=false;
	public int pixelFormat=PIXEL_A;
	public int nbStyles;
	public Style styles[]=new Style[MAX_FONT_STYLES];
	public int nbTextures;
	public Texture textures[]=new Texture[MAX_TEXTURES];

	// Data used for construction.
	// Data for the current font style.
	private Font font;
	private int nbRequestedCharacters;
	private int requestedCharacters[];		
	private BufferedImage image;
	private Graphics2D graphics;
	private FontRenderContext fontRenderContext;
	private FontMetrics fontMetrics;
	private int lastTextureY=0;

	// Test.
    public static void main(String[] args) {
        try {
        	buildTest("DejaVuLGC_Sans-regular", 20, LGC_BLOCKS, 1024, 1024, true);
        } catch(Exception e) {

    // Blocks for Latin-Greek-Cyrillic languages and some symbols.
    private static final int LGC_BLOCKS[][]={
    	{ -1, -1 },
    	{ 0x00000000, 0x0000007F }, // Basic Latin
    	{ 0x00000080, 0x000000FF }, // Latin-1 Supplement
    	{ 0x00000100, 0x0000017F }, // Latin Extended-A
    	{ 0x00000180, 0x0000024F }, // Latin Extended-B
    	{ 0x00000250, 0x000002AF }, // IPA Extensions
    	{ 0x00000370, 0x000003FF }, // Greek and Coptic
    	{ 0x00000400, 0x000004FF }, // Cyrillic
    	{ 0x00000500, 0x0000052F }, // Cyrillic Supplement
    	{ 0x00001E00, 0x00001EFF }, // Latin Extended Additional
    	{ 0x00001F00, 0x00001FFF }, // Greek Extended
    	{ 0x00002000, 0x0000206F }, // General Punctuation
    	{ 0x000020A0, 0x000020CF }, // Currency Symbols
    	{ 0x00002100, 0x0000214F }, // Letterlike Symbols
    	{ 0x00002190, 0x000021FF }, // Arrows
    	{ 0x00002200, 0x000022FF }, // Mathematical Operators
    	{ 0x000025A0, 0x000025FF }, // Geometric Shapes
    	{ 0x00002600, 0x000026FF }, // Miscellaneous Symbols
    	{ 0x00002700, 0x000027BF }, // Dingbats
    	{ 0x00002B00, 0x00002BFF }, // Miscellaneous Symbols and Arrows
    	{ 0x00002C60, 0x00002C7F }, // Latin Extended-C

    private static void buildTest(String fontName, int size, int blocks[][], int textureWidth, int textureHeight, boolean antialiasFlag) {
    	FontTextureTest fontTexture=new FontTextureTest(FontTextureTest.PIXEL_RGBA, true);
    	FontTextureTest.Parameters p=new FontTextureTest.Parameters();
    	p.textureWidth=textureWidth; p.textureHeight=textureHeight;
    	saveFontTextureImages(fontTexture, fontName, size);
    private static void saveFontTextureImages(FontTextureTest ft, String fontName, int size) {
    	for (int i=0; i<ft.nbTextures; i++) {
	        saveImage(fontName+"-"+size+"-("+i+").png", ft.textures[i].data);
    private static boolean saveImage(String filename, BufferedImage img) {
        try {
            File file=new File(filename);
            ImageIO.write(img, "png", file);
        } catch (IOException e) {
            System.out.println("Cannot write destination image.");
            return true;
        return false;

	public FontTextureTest(int pixelFormat, boolean debugFlag) {
	// Style.
    public int addStyle(Parameters p) {
    	if (nbStyles>MAX_FONT_STYLES) {
    		System.out.println("Too many font styles.");
    		return -1;
    	// Create the list of requested characters.
    	if (createCharacterList(p.blocks)) return -1;
    	// Load the font.
    	if ((font=createFont(p.fontName, p.size))==null) return -1;
        // Create an image.
        if (image==null || p.textureOption==TEXTURE_SINGLE_PER_STYLE || p.textureOption==TEXTURE_NEW_PER_STYLE) {
        	if (createTexture(p)) return -1;
        } else {
    	Style fs=new Style();

        // Retrieve font infos.
	        Rectangle2D maxCharBounds=fontMetrics.getMaxCharBounds(graphics);
	        if (fs.height>fs.maxHeight) fs.maxHeight=fs.height;

        // Build the texture and the character array.
        Glyph characters[]=new Glyph[nbRequestedCharacters];
        int nbEffectiveCharacters=0, nbUndisplayableCharacters=0, nbInvalidCharacters=0;
   	 	int characterX=0, characterY=lastTextureY;
        for (int i=0; i<nbRequestedCharacters; i++) {
        	int codePoint=requestedCharacters[i];
        	int characterWidth, characterHeight, characterAdvance;
        	GlyphVector glyph=null;
        	Rectangle characterBounds=null;
        	if (codePoint>=0) {
	        	if (!Character.isDefined(codePoint)) {
//            		System.out.println("Invalid codepoint: "+codePoint);
	        	if (!font.canDisplay(codePoint)) {
//            		System.out.println("Cannot display codepoint: "+codePoint);
	        	glyph=font.createGlyphVector(fontRenderContext, Character.toChars(codePoint));
        	} else {
        		// Missing glyph.
        		int missingGlyph[]={ font.getMissingGlyphCode() };
        		glyph=font.createGlyphVector(fontRenderContext, missingGlyph);
        	// Get character infos.
       	 	characterBounds=glyph.getGlyphPixelBounds(0, fontRenderContext, 0, fs.maxAscent);
        	characterWidth=characterBounds.width; characterHeight=characterBounds.height; characterAdvance=fontMetrics.charWidth(codePoint);        	
        	// Check if the character is valid.
        	if (characterWidth<0 || characterWidth>fs.maxWidth) {
        		System.out.println("Invalid character width: codePoint="+codePoint+"; width="+characterWidth);
        		nbInvalidCharacters++; continue;
        	if (characterHeight<0 || characterHeight>fs.maxHeight) {
        		System.out.println("Invalid character height: codePoint="+codePoint+"; height="+characterHeight);
        		nbInvalidCharacters++; continue;
        	if (characterAdvance<0 || characterAdvance>fs.maxAdvance) {
               	System.out.println("Invalid character advance: codePoint="+codePoint+"; advance="+characterAdvance);
        		nbInvalidCharacters++; continue;
           	// Create a new character.
        	Glyph gi=new Glyph();
        	boolean hasGlyph=(characterWidth>0 && characterHeight>0);
        	if (hasGlyph) {
            	// Check if the character can fit on the current line.
            	int nextCharacterX=characterX+HORIZONTAL_SPACING+characterWidth+HORIZONTAL_SPACING;
           		int nextCharacterY=characterY+VERTICAL_SPACING+fs.maxHeight+VERTICAL_SPACING;
               	if (nextCharacterX>=textures[nbTextures-1].width) {
               		if (nextCharacterY>=textures[nbTextures-1].height) {
               			if (p.textureOption==TEXTURE_SINGLE || p.textureOption==TEXTURE_SINGLE_PER_STYLE) {
                   			System.out.println("Cannot store all characters on the same texture.");
               			} else if (createTexture(p)) return -1;
               		characterX=0; nextCharacterX=HORIZONTAL_SPACING+characterWidth+HORIZONTAL_SPACING;
//            	characterHeight=fs.maxHeight;
            	gi.tx=(short)(characterX+HORIZONTAL_SPACING); gi.ty=(short)(characterY+characterBounds.y+VERTICAL_SPACING);
            	gi.tw=(short)(characterWidth); gi.th=(short)(characterHeight);
            	gi.x=(short)(characterBounds.x); gi.y=(short)(characterBounds.y-fs.maxAscent);
            	// Draw the character.
            	if (debugFlag) {
		           	graphics.drawRect(gi.tx-HORIZONTAL_SPACING, gi.ty-VERTICAL_SPACING, gi.tw-1+2*HORIZONTAL_SPACING, gi.th-1+2*VERTICAL_SPACING);
	           	if (glyph!=null) {
		            int drawX=characterX+HORIZONTAL_SPACING-characterBounds.x, drawY=characterY+VERTICAL_SPACING+fs.maxAscent;
		           	graphics.drawGlyphVector(glyph, drawX, drawY);
        	} else {
            	gi.tx=0; gi.ty=0;
            	gi.tw=0; gi.th=0;
            	gi.x=0; gi.y=0;
        System.out.println("Undisplayable: "+nbUndisplayableCharacters+"; Invalid: "+nbInvalidCharacters);
        System.out.println("Total texture height: "+characterY);
        return nbStyles-1;
    private static Font createFont(String fontName, int size) {
    	Font originalFont;
    	try {
	    	File file=new File(fontName);
	    	originalFont=Font.createFont(Font.TRUETYPE_FONT, file);
    	} catch (Exception e) {
    		System.out.println("Cannot open file: "+fontName);
    		return null;
    	Font font=originalFont.deriveFont((float)size);
    	System.out.println("Building: "+fontName);
    	System.out.println("Family: "+font.getFamily()+"; Face name: "+font.getFontName()+"; Logical name: "+font.getName()+"; Size: "+font.getSize());
    	System.out.println("Num glyphs: "+font.getNumGlyphs());	
        return font;
    private boolean createCharacterList(int blocks[][]) {
        // Count characters.
        for (int i=0; i<blocks.length; i++) {
        	int n=blocks[i][1]-blocks[i][0];
        	if (n<0) {
        		System.out.println("Invalid block: "+i+"; Range: "+blocks[i][0]+"-"+blocks[i][1]);
        		return true;
        if (nbRequestedCharacters<=0) {
        	System.out.println("The number of characters is 0.");
        	return true;
        // Build characters list.
        requestedCharacters=new int[nbRequestedCharacters];
	        int k=0;
	        for (int i=0; i<blocks.length; i++) {
	        	int n=blocks[i][1]-blocks[i][0];
	        	for (int j=0; j<=n; j++) requestedCharacters[k+j]=blocks[i][0]+j;

        System.out.println("Requested characters: "+nbRequestedCharacters);
        return false;
    private boolean createTexture(Parameters p) {
    	if (nbTextures>=MAX_TEXTURES) {
    		System.out.println("Too many textures");
    		return true;
    	switch (pixelFormat) {
    	case PIXEL_A:
        	image=new java.awt.image.BufferedImage(p.textureWidth, p.textureHeight, BufferedImage.TYPE_BYTE_GRAY);
    	case PIXEL_RGBA:
        	image=new java.awt.image.BufferedImage(p.textureWidth, p.textureHeight, BufferedImage.TYPE_4BYTE_ABGR);
        default: return true;
    	graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        if (p.antialiasFlag) {
        	graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        	graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
    	Texture texture=new Texture();
    	texture.width=p.textureWidth; texture.height=p.textureHeight;
    	return false;

    // Infos.
    private static final String PIXEL_FORMAT_STRING[]={ "A", "RGBA" };
    public void printInfos() {
    	System.out.println("Pixel format: "+PIXEL_FORMAT_STRING[pixelFormat]);
    	System.out.println("Num styles: "+nbStyles);
    	System.out.println("Num textures: "+nbTextures);
    	for (int i=0; i<nbStyles; i++) {
    		if (styles[i]!=null) printInfos(i);
    public void printInfos(int f) {
    	Style fs=styles[f];
    	System.out.println("Num characters: "+fs.nbCharacters);
    	System.out.println("Ascent: "+fs.ascent+"; Descent: "+fs.descent+"; Leading: "+fs.leading+"; Height: "+fs.height);
    	System.out.println("Max ascent: "+fs.maxAscent+"; Max descent: "+fs.maxDescent+"; Max advance: "+fs.maxAdvance);    	
    	System.out.println("Max width: "+fs.maxWidth+"; Max height: "+fs.maxHeight);
    public void printInfos(Glyph ci) {
    	System.out.println("codepoint: "+ci.codePoint+"; tx="+ci.tx+"; ty="+ci.ty+"; tw="+ci.tw+"; th="+ci.th+"; x="+ci.x+"; y="+ci.y);
Comment 3 Denis Jacquerye 2007-03-17 02:26:46 UTC
 > Here is the source code of a modified program:
Could you create a new attachment? The code is broken with the reformatting.
Comment 4 Mathieu Legris 2007-03-17 03:13:44 UTC
Created attachment 9201 [details]
Source code of a program that generates font texture
Comment 5 Mathieu Legris 2007-03-17 03:16:24 UTC
Created attachment 9202 [details]
Output Image showing incorrectly placed glyphs
Comment 6 Mathieu Legris 2007-03-17 03:19:22 UTC
Comment on attachment 9202 [details]
Output Image showing incorrectly placed glyphs

Bugzilla does not display correctly this image probably because of the alpha channel.
Use Gimp with alpha activated to view this image.
Comment 7 Denis Jacquerye 2007-04-11 11:17:24 UTC
(In reply to comment #6)
> (From update of attachment 9202 [details])
Looking at the attachment, it seems Java is getting mixed up when base and diacritics are not in a specific order.

btw, I'm getting a code dump when running the class.

Comment 8 Mathieu Legris 2007-04-11 15:19:14 UTC
Created attachment 9568 [details]
Package with source code, font and jar file
Comment 9 Mathieu Legris 2007-04-11 15:22:26 UTC
(In reply to comment #7)
> (In reply to comment #6)
> > (From update of attachment 9202 [details] [details])
> Looking at the attachment, it seems Java is getting mixed up when base and
> diacritics are not in a specific order.
> btw, I'm getting a code dump when running the class.

I am really sorry. I quickly extracted the code from my projects without giving any documentation and forgiving that the others have other stuff to do...

This time I send a full package with a jar inside:
-Unzip the package
-Go inside the package directory
-Run the jar:
java -jar FontTexture.jar DejaVuLGC_Sans-regular.ttf

Obviously, you can use other fonts.

You should check that you are using the Sun implementation of Java if you are under Linux because I have not tested this program with the GNU implementation.

Use of freedesktop.org services, including Bugzilla, is subject to our Code of Conduct. How we collect and use information is described in our Privacy Policy.