Hello, 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.
Thank you Mathieu. I'm a bit confused with the advance, height or width given. They sound really wrong. What unit are they in?
(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_REGULAR= 0, STYLE_BOLD= 1, STYLE_ITALIC= 2, STYLE_BOLD_ITALIC= 3; 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) { e.printStackTrace(); return; } } // 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.size=size; p.blocks=blocks; p.textureWidth=textureWidth; p.textureHeight=textureHeight; p.textureOption=FontTextureTest.TEXTURE_SINGLE_PER_STYLE; p.antialiasFlag=antialiasFlag; p.fontName=fontName+".ttf"; fontTexture.addStyle(p); fontTexture.printInfos(); 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) { this.pixelFormat=pixelFormat; this.debugFlag=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 { graphics.setFont(font); fontRenderContext=graphics.getFontRenderContext(); fontMetrics=graphics.getFontMetrics(); } Style fs=new Style(); // Retrieve font infos. { Rectangle2D maxCharBounds=fontMetrics.getMaxCharBounds(graphics); fs.ascent=fontMetrics.getAscent(); fs.descent=fontMetrics.getDescent(); fs.leading=fontMetrics.getLeading(); fs.height=fontMetrics.getHeight(); fs.maxAscent=fontMetrics.getMaxAscent(); fs.maxDescent=fontMetrics.getMaxDescent(); fs.maxAdvance=fontMetrics.getMaxAdvance(); fs.maxWidth=(int)Math.ceil(maxCharBounds.getMaxX()-maxCharBounds.getMinX()); fs.maxHeight=(int)Math.ceil(maxCharBounds.getMaxY()-maxCharBounds.getMinY()); 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); continue; } if (!font.canDisplay(codePoint)) { // System.out.println("Cannot display codepoint: "+codePoint); nbUndisplayableCharacters++; continue; } 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(); characters[nbEffectiveCharacters++]=gi; gi.codePoint=codePoint; gi.advance=(short)characterAdvance; 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."); break; } else if (createTexture(p)) return -1; nextCharacterY=lastTextureY; } characterX=0; nextCharacterX=HORIZONTAL_SPACING+characterWidth+HORIZONTAL_SPACING; characterY=nextCharacterY; } // 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); gi.texture=(short)(nbTextures-1); // Draw the character. if (debugFlag) { graphics.setColor(Color.RED); 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.setColor(Color.WHITE); graphics.drawGlyphVector(glyph, drawX, drawY); } characterX=nextCharacterX; } else { gi.tx=0; gi.ty=0; gi.tw=0; gi.th=0; gi.x=0; gi.y=0; gi.texture=(short)(-1); } } fs.nbCharacters=nbEffectiveCharacters; fs.characters=characters; styles[nbStyles++]=fs; characterY+=VERTICAL_SPACING+fs.maxHeight+VERTICAL_SPACING; lastTextureY=characterY; System.out.println("Undisplayable: "+nbUndisplayableCharacters+"; Invalid: "+nbInvalidCharacters); System.out.println("Total texture height: "+characterY); System.out.println(); 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. nbRequestedCharacters=0; 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; } nbRequestedCharacters+=n+1; } 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; k+=n+1; } } 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); break; case PIXEL_RGBA: image=new java.awt.image.BufferedImage(p.textureWidth, p.textureHeight, BufferedImage.TYPE_4BYTE_ABGR); break; default: return true; } lastTextureY=0; graphics=image.createGraphics(); 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); } graphics.setFont(font); fontRenderContext=graphics.getFontRenderContext(); fontMetrics=graphics.getFontMetrics(); Texture texture=new Texture(); textures[nbTextures++]=texture; texture.width=p.textureWidth; texture.height=p.textureHeight; texture.data=image; 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); System.out.println(); 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); System.out.println(); } 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); } }
> Here is the source code of a modified program: Could you create a new attachment? The code is broken with the reformatting.
Created attachment 9201 [details] Source code of a program that generates font texture
Created attachment 9202 [details] Output Image showing incorrectly placed glyphs
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.
(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.
Created attachment 9568 [details] Package with source code, font and jar file
(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.