BufferUnderFlowException beim laden eines Fonts

Moin,

nachdem ich Wochenlang meine GUI-API für Slick geschrieben hab, bin ich soweit für meine Zwecke fertig, allerdings crasht mir das Spiel
aus unerfindlichen Gründen immer wieder, wenn die Zelle das Boolean AutoAdjust auf true hat. Dieses Boolean bewirkt, das wenn das Font der Zelle die Zellenmaße
sprengt, die Fontgröße per Dreisatz auf den idealen Grenzwert gebracht wird. Dabei muss, aus welchen Gründen auch immer, ein neues Fontobjekt erstellt werden(Ich
überschreibe das alte einfach!). Dabei kommt eben dieser Fehler:

Thu Sep 26 22:23:27 CEST 2013 ERROR:Invalid font: 
org.newdawn.slick.SlickException: Invalid font: 
	at org.newdawn.slick.UnicodeFont.createFont(UnicodeFont.java:63)
	at org.newdawn.slick.UnicodeFont.<init>(UnicodeFont.java:173)
	at de.frostbyteger.pong.engine.graphics.FontHelper.newFont(FontHelper.java:18)
	at de.frostbyteger.pong.engine.graphics.ui.gui.Cell.drawCell(Cell.java:253)
	at de.frostbyteger.pong.core.MainMenu.render(MainMenu.java:75)
	at org.newdawn.slick.state.StateBasedGame.render(StateBasedGame.java:199)
	at org.newdawn.slick.GameContainer.updateAndRender(GameContainer.java:688)
	at org.newdawn.slick.AppGameContainer.gameLoop(AppGameContainer.java:411)
	at org.newdawn.slick.AppGameContainer.start(AppGameContainer.java:321)
	at de.frostbyteger.pong.start.Pong.main(Pong.java:120)
Caused by: java.awt.FontFormatException: java.nio.BufferUnderflowException
	at sun.font.TrueTypeFont.init(TrueTypeFont.java:558)
	at sun.font.TrueTypeFont.<init>(TrueTypeFont.java:191)
	at sun.font.SunFontManager.createFont2D(SunFontManager.java:2460)
	at java.awt.Font.<init>(Font.java:614)
	at java.awt.Font.createFont0(Font.java:968)
	at java.awt.Font.createFont(Font.java:876)
	at org.newdawn.slick.UnicodeFont.createFont(UnicodeFont.java:61)
	... 9 more
Thu Sep 26 22:23:27 CEST 2013 ERROR:Game.render() failure - check the game code.
org.newdawn.slick.SlickException: Game.render() failure - check the game code.
	at org.newdawn.slick.GameContainer.updateAndRender(GameContainer.java:691)
	at org.newdawn.slick.AppGameContainer.gameLoop(AppGameContainer.java:411)
	at org.newdawn.slick.AppGameContainer.start(AppGameContainer.java:321)
	at de.frostbyteger.pong.start.Pong.main(Pong.java:120)

Weiß jemand warum? Ich konnte bisher beobachten, das wenn ich die Fontgröße von vorneherein etwas verkleinere, der Fehler zumindest für diese Zelle nichtmehr auftritt.
Lustigerweise muss ich die Zelle, die danach erstellt wird, noch etwas weiter verkleinern, also die Fontgröße. Irgendwann würden dann negative Werte rauskommen!

Zum besseren Verständnis hier die FontHelper Klasse und das AutoAdjust Segment:
FontHelper:

public class FontHelper {
	
	@SuppressWarnings("unchecked")
	public static UnicodeFont newFont(String font,int fontsize, boolean bold, boolean italic) throws SlickException{
		UnicodeFont unifont = new UnicodeFont(font,fontsize , bold, italic);
		unifont.addAsciiGlyphs();
		unifont.getEffects().add(new ColorEffect());
		unifont.loadGlyphs();
		return unifont;
	}

}

AutoAdjust Segment:

				if(autoAdjust == true){ //TODO: Add algorithm for increasing fontsize 
					if(cellFont != null){
						if(cellFont.getWidth(cellText) >= cell.getWidth()) {
							float i = (float)cellFont.getWidth(cellText) / (float)size;
							if(cell.getWidth() > cellDrawOffsetX){
								size = (int) ((cell.getWidth() - cellDrawOffsetX) / i);			
							}else{
								size = (int) (cell.getWidth() / i);			
							}
							cellFont = FontHelper.newFont(fontPath, size, bold, italic);
						}else if(cellFont.getHeight(cellText) >= cell.getHeight()){ //Im Normalfall wird dieser Abschnitt nicht ausgeführt, da das Font eher die Breite sprengt, als die Höhe!
							float i = (float)cellFont.getHeight(cellText) / (float)size;
							if(cell.getHeight() > cellDrawOffsetY){
								size = (int) ((cell.getHeight() - cellDrawOffsetY) / i);							
							}else{
								size = (int) (cell.getHeight() / i);							
							}
							cellFont = FontHelper.newFont(fontPath, size, bold, italic);
						}
					}
				}

Da schlägt offensichtlich schon das Laden bzw. initialisieren der Fontdatei (TrueTypeFont) fehl. An der Stelle wo die Exception geworfen wird, sollte es aber eigentlich keine Probleme geben, es sei denn, die Fontdatei ist defekt. TrueType wird auf jeden Fall schon vorher im FontManager erkannt bzw noch viel früher, wenn man es bei “Font.createFont()” als Parameter übergibt.
Was ist das für ein Font und in welcher Größe willst du ihn öffnen?

edit: Wenn die Zeilenzahlen im Stactrace mit denen aus der aktuellen API-Source übereinstimmen, versucht “UnicodeFont.createFont()” die Methode “Font.createFont(int type, InputStream fontStream)” aufzurufen, wobei “type” dann TrueType bedeutet. Diese Methode kopiert den FontStream in eine temporäre Datei welche dann an den Fontmanager übergeben wird. Wenn der Stream aber bereits bis zum Ende gelesen wurde (z.B. durch vorheriges Laden des Fonts) entsteht so eine leere Datei deren Stream an den TrueType-Loader übergeben wird. In dem Loader wird das Format selber nicht mehr geprüft sondern sofort versucht es zu lesen, was bei einem leeren Stream zu diesem BufferUnderFlow führt. Kurz und Bündig: Der Stream sollte zurück an den Anfang gesetzt oder die “createFont()”-Methode, der man ein File-Objekt übergeben kann verwendet werden.

edit2: Machs dir einfacher. Lade die Fonts mit Standard-Java und benutze “new UnicodeFont(javaFont)”.

Habs jetzt mal versucht. Es ist erstaunlich kompliziert ein Font zu erzeugen und dessen Eigenschaften(vorallem die Größe!)


import java.awt.Font;
import java.awt.FontFormatException;
import java.io.FileInputStream;
import java.io.IOException;

import org.newdawn.slick.SlickException;
import org.newdawn.slick.UnicodeFont;
import org.newdawn.slick.font.effects.ColorEffect;

/**
 * @author Kevin Kuegler
 * @version 1.00
 *
 */
public class FontHelper {
	
	/**
	 * Creates a new font which has a specific style and a size.
	 * Use the following integers for different styles:
	 * 0 = plain
	 * 1 = bold
	 * 2 = italic
	 * @param fontPath
	 * @param style The style you want
	 * @param fontSize
	 * @return
	 * @throws SlickException
	 */
	@SuppressWarnings("unchecked")
	public static UnicodeFont newFont(String fontPath,int style, int fontSize) throws SlickException{
		Font font = null;
		try {
			font = Font.createFont(Font.TRUETYPE_FONT, new FileInputStream(fontPath));
		} catch (FontFormatException | IOException e) {
			e.printStackTrace();
		}
		Font font2 = font.deriveFont((float)fontSize);
		UnicodeFont unifont = new UnicodeFont(font2);
		unifont.addAsciiGlyphs();
		unifont.getEffects().add(new ColorEffect());
		unifont.loadGlyphs();
		return unifont;
	}

}```

Allerdings kommt es immernoch zu einer Exception:
```java.io.FileNotFoundException: 
Sun Sep 29 22:39:38 CEST 2013 ERROR:null
java.lang.NullPointerException
	at java.io.FileInputStream.open(Native Method)
	at java.io.FileInputStream.<init>(FileInputStream.java:138)
	at java.io.FileInputStream.<init>(FileInputStream.java:97)
	at de.frostbyteger.pong.engine.graphics.FontHelper.newFont(FontHelper.java:38)
	at de.frostbyteger.pong.engine.graphics.ui.gui.Cell.drawCell(Cell.java:251)
	at de.frostbyteger.pong.core.Options.render(Options.java:234)
	at org.newdawn.slick.state.StateBasedGame.render(StateBasedGame.java:199)
	at org.newdawn.slick.GameContainer.updateAndRender(GameContainer.java:688)
	at org.newdawn.slick.AppGameContainer.gameLoop(AppGameContainer.java:411)
	at org.newdawn.slick.AppGameContainer.start(AppGameContainer.java:321)
	at de.frostbyteger.pong.start.Pong.main(Pong.java:120)
	at de.frostbyteger.pong.engine.graphics.FontHelper.newFont(FontHelper.java:42)
	at de.frostbyteger.pong.engine.graphics.ui.gui.Cell.drawCell(Cell.java:251)
	at de.frostbyteger.pong.core.Options.render(Options.java:234)
	at org.newdawn.slick.state.StateBasedGame.render(StateBasedGame.java:199)
	at org.newdawn.slick.GameContainer.updateAndRender(GameContainer.java:688)
	at org.newdawn.slick.AppGameContainer.gameLoop(AppGameContainer.java:411)
	at org.newdawn.slick.AppGameContainer.start(AppGameContainer.java:321)
	at de.frostbyteger.pong.start.Pong.main(Pong.java:120)
Sun Sep 29 22:39:38 CEST 2013 ERROR:Game.render() failure - check the game code.
org.newdawn.slick.SlickException: Game.render() failure - check the game code.
	at org.newdawn.slick.GameContainer.updateAndRender(GameContainer.java:691)
	at org.newdawn.slick.AppGameContainer.gameLoop(AppGameContainer.java:411)
	at org.newdawn.slick.AppGameContainer.start(AppGameContainer.java:321)
	at de.frostbyteger.pong.start.Pong.main(Pong.java:120)```

Ich muss zugeben, jetzt bin ich verwirrt. Denn der Pfad zur Datei ist derselbe!
Der Fehler tritt auch erst viel später auf! Nicht erst beim Start des Programms(Das Hauptmenü wird mit demselben Font-Pfad geladen und auch
korrekt angezeigt), sondern erst beim Aufruf der Grafik-Optionen! Die Größe des Fonts variiert je nach Verwendung, hier die Grafikfonts:
```		// Global Header
		mainHeader = new Cell(Pong.FONT, 160, Pong.S_resX/2 - 350/2, 20, 350, 250, container);
		mainHeader.setAutoAdjust(false);
		mainHeader.setCellText(PONG);
		mainHeader.setClickable(false);
		
		// Local Headers
		optionHeader = new Cell(Pong.FONT, 60, OFFSET_X, Pong.S_resY/2 - 100, 250, 100, container);
		optionHeader.setLeft();
		optionHeader.setCellText(OPTIONS);
		optionHeader.setFontColor(Color.cyan);
		optionHeader.setClickable(false);
		
		graphicsHeader = new Cell(Pong.FONT, 60, OFFSET_X, Pong.S_resY/2 - 100, 250, 100, container);
		//graphicsHeader.setAutoAdjust(false);
		graphicsHeader.setLeft();
		graphicsHeader.setCellText(MENU_OPTIONS_MAIN[0]);
		graphicsHeader.setFontColor(Color.cyan);
		graphicsHeader.setClickable(false);
		
		controlHeader = new Cell(Pong.FONT, 60, OFFSET_X, Pong.S_resY/2 - 100, 250, 100, container);
		//controlHeader.setAutoAdjust(false);
		controlHeader.setLeft();
		controlHeader.setCellText(MENU_OPTIONS_MAIN[1]);
		controlHeader.setFontColor(Color.cyan);
		controlHeader.setClickable(false);
		
		networkHeader = new Cell(Pong.FONT, 60, OFFSET_X, Pong.S_resY/2 - 100, 250, 100, container);
		//networkHeader.setAutoAdjust(false);
		networkHeader.setLeft();
		networkHeader.setCellText(MENU_OPTIONS_MAIN[2]);
		networkHeader.setFontColor(Color.cyan);
		networkHeader.setClickable(false);
		
		saveCell = new Cell(Pong.FONT, 30, OFFSET_X, Pong.S_resY/2 + 125, 100, 30, container);
		//saveCell.setAutoAdjust(false);
		saveCell.setLeft();
		saveCell.setCellText("Options saved!");
		saveCell.setFontColor(Color.green);
		saveCell.setClickable(false);```
(Nebenbei fällt mir auf, ich könnte den Aschnitt sogar mit ner Schleife lösen^^)

Zumindest ist jetzt klar, warum der Stream im ersten Post nicht als FontStream geschweige denn als TrueTypeFontStream erkannt wurde. Die NullPointerException im 2. Post erklärt sich dadurch, dass der FontFile anscheinend wirklich nicht vorhanden ist. Da es ohnehin angebrachter ist, “createFont(int type, File fontFile)” zu verwenden, weil Streams wie gesagt ohnehin nochmal temporär als File gespeichert werden, kann man vorher mit “.exists()” testen, ob sich der FontFile wirklich dort befindet, wo man ihn vermutet. Wenn sich der Font aber im Jar-Verzeichnis (z.B. in Eclipse im src/bin-Ordner) oder gar im Jar befindet, muss man es doch schon so (ungefähr) machen, wie es Slick auch macht: “ClassLoader.getSystemRessourceAsStream()”.

Tatsache, mit f.exists() gibt er bei einem Mal plötzlich false zurück. Aber warum? Wird bei einem FileInputStream die Datei gesperrt? Selbst wenn ja, dann müsste es doch schon beim ersten Mal gleich ne Exception hageln, denn vorher gibt f.exists() immer true zurück(insgesamt ca. 10 mal), doch mit einem Mal nichtmehr.

Edit: Shame on me. Hab gerade mal im Debugger nachgeguckt. Meine Cell class hat mehrere Konstruktoren, damit man nicht gleich tausende Methoden manuell aufrufen muss.
Leider hab ich bei eben genau bei dem vergessen, die Klassenvariable „fontPath“ zu setten. Sie wird zwar mit übergeben, aber eben nicht mit der Klassenvariable verlinkt.
Daher versucht natürlich die Methode newFont(…) mit nem leeren String zu arbeiten.

Daher: Verlinken, Problem gelöst. Das manuelle erstellen des Fonts per Standard-API kann ich mir so, mehr oder weniger, auch sparen.

Danke dennoch für die Hilfe :slight_smile:

Sollte jemanden mein Projekt interessieren, ich stelle es bald online und poste den Link hier!