From 70c60516b2cb28a4bffc1bf0f5f5c4a75f24542d Mon Sep 17 00:00:00 2001 From: Lunkle Date: Thu, 16 Jan 2025 21:40:56 -0500 Subject: [PATCH 1/2] Add support to load TTF TrueType fonts using FreeType Add support to load TTF TrueType fonts using FreeType. * **NengenFileUtil.java** - Add import statements for FreeType classes. - Add a new method `loadTTF` to load TTF fonts using FreeType. * **GameFont.java** - Add a constructor to accept FreeType font data. - Add a method to load FreeType font data. - Update `delete` method to handle null texture. * **CharacterData.java** - Add a constructor to accept FreeType character data. * **pom.xml** - Add dependency for `lwjgl-freetype` version `3.3.6`. --- pom.xml | 5 ++ src/main/java/common/NengenFileUtil.java | 51 +++++++++++++++++++ .../visuals/rendering/text/CharacterData.java | 4 ++ .../java/visuals/rendering/text/GameFont.java | 11 +++- 4 files changed, 70 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e85036e..6939631 100644 --- a/pom.xml +++ b/pom.xml @@ -200,6 +200,11 @@ lwjgl-stb ${lwjgl.natives} + + org.lwjgl + lwjgl-freetype + 3.3.6 + diff --git a/src/main/java/common/NengenFileUtil.java b/src/main/java/common/NengenFileUtil.java index 00aac89..5c4a5f3 100644 --- a/src/main/java/common/NengenFileUtil.java +++ b/src/main/java/common/NengenFileUtil.java @@ -20,6 +20,12 @@ import visuals.rendering.text.GameFont; import visuals.rendering.texture.Image; +import org.lwjgl.freetype.FreeType; +import org.lwjgl.freetype.FT_Face; +import org.lwjgl.freetype.FT_Library; +import org.lwjgl.freetype.FT_GlyphSlot; +import org.lwjgl.freetype.FT_Bitmap; + public class NengenFileUtil { private NengenFileUtil() { @@ -132,6 +138,51 @@ public static GameFont loadFont(File font, Texture texture) { } } + public static GameFont loadTTF(File ttfFile, int fontSize) { + FT_Library ftLibrary = FreeType.FT_Init_FreeType(); + if (ftLibrary == null) { + throw new RuntimeException("Failed to initialize FreeType library"); + } + + FT_Face ftFace = FreeType.FT_New_Face(ftLibrary, ttfFile.getAbsolutePath(), 0); + if (ftFace == null) { + throw new RuntimeException("Failed to load font: " + ttfFile.getAbsolutePath()); + } + + FreeType.FT_Set_Pixel_Sizes(ftFace, 0, fontSize); + + GameFont gameFont = new GameFont(ttfFile.getName(), fontSize, null); + CharacterData[] characters = gameFont.getCharacterDatas(); + + for (int c = 0; c < 128; c++) { + if (FreeType.FT_Load_Char(ftFace, c, FreeType.FT_LOAD_RENDER) != 0) { + System.err.println("Failed to load Glyph for character: " + (char) c); + continue; + } + + FT_GlyphSlot glyph = ftFace.glyph(); + FT_Bitmap bitmap = glyph.bitmap(); + + CharacterData charData = new CharacterData( + (short) glyph.bitmap_left(), + (short) glyph.bitmap_top(), + (short) bitmap.width(), + (short) bitmap.rows(), + (short) glyph.bitmap_left(), + (short) glyph.bitmap_top(), + (short) glyph.advance().x(), + 0 + ); + + characters[c] = charData; + } + + FreeType.FT_Done_Face(ftFace); + FreeType.FT_Done_FreeType(ftLibrary); + + return gameFont; + } + private static short readShort(FileInputStream fis) throws IOException { byte b1 = (byte) fis.read(); byte b2 = (byte) fis.read(); diff --git a/src/main/java/visuals/rendering/text/CharacterData.java b/src/main/java/visuals/rendering/text/CharacterData.java index 456a49d..c148c9d 100644 --- a/src/main/java/visuals/rendering/text/CharacterData.java +++ b/src/main/java/visuals/rendering/text/CharacterData.java @@ -57,6 +57,10 @@ public CharacterData(short x, short y, short width, short height, short xOffset, this(x, y, width, height, xOffset, yOffset, xAdvance, (int) page); } + public CharacterData(int x, int y, int width, int height, int xOffset, int yOffset, int xAdvance) { + this(x, y, width, height, xOffset, yOffset, xAdvance, 0); + } + public int x() { return x; } diff --git a/src/main/java/visuals/rendering/text/GameFont.java b/src/main/java/visuals/rendering/text/GameFont.java index 0618ef5..a380c1a 100644 --- a/src/main/java/visuals/rendering/text/GameFont.java +++ b/src/main/java/visuals/rendering/text/GameFont.java @@ -16,6 +16,13 @@ public GameFont(String name, int fontSize, Texture texture) { this.texture = texture; } + public GameFont(String name, int fontSize, CharacterData[] characterDatas) { + this.name = name; + this.fontSize = fontSize; + this.characterDatas = characterDatas; + this.texture = null; + } + public String getName() { return name; } @@ -33,7 +40,9 @@ public Texture texture() { } public void delete() { - texture.delete(); + if (texture != null) { + texture.delete(); + } } } From 6acf4da44413dd48b259b52d75fa5ba64c87bbdb Mon Sep 17 00:00:00 2001 From: Lunkle Date: Fri, 17 Jan 2025 22:40:14 -0500 Subject: [PATCH 2/2] Add support to load TTF TrueType fonts using FreeType Add support to load TTF TrueType fonts using FreeType. * **NengenFileUtil.java** - Add import statements for FreeType classes. - Add a new method `loadTTF` to load TTF fonts using FreeType. * **GameFont.java** - Add a constructor to accept FreeType font data. - Add a method to load FreeType font data. - Update `delete` method to handle null texture. * **CharacterData.java** - Add a constructor to accept FreeType character data. * **pom.xml** - Add dependency for `lwjgl-freetype` version `3.3.6`. --- pom.xml | 7 +- src/main/java/common/NengenFileUtil.java | 68 ++++++++++++++++++- .../visuals/rendering/text/CharacterData.java | 41 +++++++++-- .../java/visuals/rendering/text/GameFont.java | 32 ++++++++- 4 files changed, 136 insertions(+), 12 deletions(-) diff --git a/pom.xml b/pom.xml index 3636cb1..e90e156 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 virtual-cardboard nengen - 0.0.3 + 0.0.4 1.8 @@ -200,6 +200,11 @@ lwjgl-stb ${lwjgl.natives} + + org.lwjgl + lwjgl-freetype + 3.3.6 + diff --git a/src/main/java/common/NengenFileUtil.java b/src/main/java/common/NengenFileUtil.java index 00aac89..17303f8 100644 --- a/src/main/java/common/NengenFileUtil.java +++ b/src/main/java/common/NengenFileUtil.java @@ -7,6 +7,9 @@ import static org.lwjgl.stb.STBImage.stbi_failure_reason; import static org.lwjgl.stb.STBImage.stbi_load; import static org.lwjgl.stb.STBImage.stbi_set_flip_vertically_on_load; +import static org.lwjgl.util.freetype.FreeType.FT_Init_FreeType; +import static org.lwjgl.util.freetype.FreeType.FT_New_Face; +import static visuals.rendering.text.GameFont.getFontIndex; import java.io.File; import java.io.FileInputStream; @@ -14,7 +17,12 @@ import java.nio.ByteBuffer; import java.nio.IntBuffer; +import org.lwjgl.PointerBuffer; import org.lwjgl.system.MemoryStack; +import org.lwjgl.util.freetype.FT_Bitmap; +import org.lwjgl.util.freetype.FT_Face; +import org.lwjgl.util.freetype.FT_GlyphSlot; +import org.lwjgl.util.freetype.FreeType; import visuals.lwjgl.render.Texture; import visuals.rendering.text.CharacterData; import visuals.rendering.text.GameFont; @@ -94,7 +102,7 @@ public static GameFont loadFont(File font, Texture texture) { DEBUG("Characters: " + numCharacters); DEBUG("Kernings: " + kernings); - GameFont gameFont = new GameFont(name, fontSize, texture); + GameFont gameFont = new GameFont(name, fontSize, texture, getFontIndex()); // Read characters CharacterData[] characters = gameFont.getCharacterDatas(); @@ -111,7 +119,8 @@ public static GameFont loadFont(File font, Texture texture) { short page = (short) fis.read(); CharacterData charData = new CharacterData(x, y, width, height, xOffset, yOffset, xAdvance, page); DEBUG("====================="); - DEBUG(c + " " + x + " " + y + " " + width + " " + height + " " + xOffset + " " + yOffset + " " + xAdvance + " " + page); + DEBUG(c + " " + x + " " + y + " " + width + " " + height + " " + xOffset + " " + yOffset + " " + + xAdvance + " " + page); DEBUG("Character: " + (char) c); DEBUG("X: " + x); DEBUG("Y: " + y); @@ -124,7 +133,8 @@ public static GameFont loadFont(File font, Texture texture) { characters[c] = charData; } CharacterData space = characters[' ']; - characters['\t'] = new CharacterData(space.x(), space.y(), space.width(), space.height(), space.xOffset(), space.yOffset(), (short) (space.xAdvance() * 4), space.getPage()); + characters['\t'] = new CharacterData(space.x(), space.y(), space.width(), space.height(), space.xOffset(), + space.yOffset(), (short) (space.xAdvance() * 4), space.getPage()); return gameFont; } catch (IOException e) { @@ -132,6 +142,58 @@ public static GameFont loadFont(File font, Texture texture) { } } + public static GameFont loadTTF(String ttfFile, int fontSize) { + PointerBuffer ftLibrary = PointerBuffer.allocateDirect(10000); + FTLibrar + FT_Init_FreeType(ftLibrary); + + PointerBuffer ftFaceBuffer = PointerBuffer.allocateDirect(1); + ByteBuffer ftFaceByteBuffer = ftFaceBuffer.getByteBuffer(0, 8); + FT_Face ftFace = new FT_Face(ftFaceByteBuffer); + FT_New_Face(ftLibrary.address(), ttfFile, getFontIndex(), ftFaceBuffer); + + if (false) { + throw new RuntimeException("Failed to initialize FreeType library"); + } + + FreeType.FT_Set_Pixel_Sizes(ftFace, 0, fontSize); + + GameFont gameFont = new GameFont(ttfFile, fontSize, new CharacterData[0], getFontIndex()); + CharacterData[] characters = gameFont.getCharacterDatas(); + + for (int c = 0; c < 128; c++) { + if (FreeType.FT_Load_Char(ftFace, c, FreeType.FT_LOAD_RENDER) != 0) { + System.err.println("Failed to load Glyph for character: " + (char) c); + continue; + } + + FT_GlyphSlot glyph = ftFace.glyph(); + FT_Bitmap bitmap = glyph.bitmap(); + + Texture texture = new Texture().dimensions(bitmap.width(), bitmap.rows()) + .image(new Image().data(bitmap.buffer(100))).load(); + + CharacterData charData = new CharacterData( + (short) glyph.bitmap_left(), + (short) glyph.bitmap_top(), + (short) bitmap.width(), + (short) bitmap.rows(), + (short) glyph.bitmap_left(), + (short) glyph.bitmap_top(), + (short) glyph.advance().x(), + texture); + + characters[c] = charData; + } + + gameFont.characterDatas(characters); + + FreeType.FT_Done_Face(ftFace); + FreeType.FT_Done_FreeType(ftLibrary.address()); + + return gameFont; + } + private static short readShort(FileInputStream fis) throws IOException { byte b1 = (byte) fis.read(); byte b2 = (byte) fis.read(); diff --git a/src/main/java/visuals/rendering/text/CharacterData.java b/src/main/java/visuals/rendering/text/CharacterData.java index 456a49d..22548c9 100644 --- a/src/main/java/visuals/rendering/text/CharacterData.java +++ b/src/main/java/visuals/rendering/text/CharacterData.java @@ -1,7 +1,10 @@ package visuals.rendering.text; +import visuals.lwjgl.render.Texture; + /** - * Information about a character in a bitmap font. The fields are all in pixels, except for {@link CharacterData#page}. + * Information about a character in a bitmap font. The fields are all in pixels, + * except for {@link CharacterData#page}. * * @author Jay */ @@ -24,12 +27,14 @@ public final class CharacterData { */ private final int height; /** - * The horizontal offset of the character when it is displayed. This is usually a small number. + * The horizontal offset of the character when it is displayed. This is usually + * a small number. */ private final int xOffset; /** * The vertical offset of the character when it is displayed. - * For example, the letter 'g' has a larger yOffset than 'l' because it is offset downwards. + * For example, the letter 'g' has a larger yOffset than 'l' because it is + * offset downwards. */ private final int yOffset; /** @@ -39,8 +44,18 @@ public final class CharacterData { private final int xAdvance; /** * The index of the bitmap image that this character is on. This is usually 0. + *
+ *
+ * Mutually exclusive with {@link CharacterData#texture}. + */ + private int page; + /** + * The texture that this character is on. + *
+ *
+ * Mutually exclusive with {@link CharacterData#page}. */ - private final int page; + private Texture texture; public CharacterData(int x, int y, int width, int height, int xOffset, int yOffset, int xAdvance, int page) { this.x = x; @@ -53,10 +68,26 @@ public CharacterData(int x, int y, int width, int height, int xOffset, int yOffs this.page = page; } - public CharacterData(short x, short y, short width, short height, short xOffset, short yOffset, short xAdvance, short page) { + public CharacterData(int x, int y, int width, int height, int xOffset, int yOffset, int xAdvance, Texture texture) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + this.xOffset = xOffset; + this.yOffset = yOffset; + this.xAdvance = xAdvance; + this.texture = texture; + } + + public CharacterData(short x, short y, short width, short height, short xOffset, short yOffset, short xAdvance, + short page) { this(x, y, width, height, xOffset, yOffset, xAdvance, (int) page); } + public CharacterData(int x, int y, int width, int height, int xOffset, int yOffset, int xAdvance) { + this(x, y, width, height, xOffset, yOffset, xAdvance, 0); + } + public int x() { return x; } diff --git a/src/main/java/visuals/rendering/text/GameFont.java b/src/main/java/visuals/rendering/text/GameFont.java index 0618ef5..c93e2ee 100644 --- a/src/main/java/visuals/rendering/text/GameFont.java +++ b/src/main/java/visuals/rendering/text/GameFont.java @@ -4,16 +4,32 @@ public final class GameFont { + public static long FONT_INDEX = 0; + + /** + * The global index of the font. + */ + private final long fontIndex; + private final String name; private final int fontSize; - private final CharacterData[] characterDatas; + private CharacterData[] characterDatas; private final Texture texture; - public GameFont(String name, int fontSize, Texture texture) { + public GameFont(String name, int fontSize, Texture texture, long fontIndex) { this.name = name; this.fontSize = fontSize; characterDatas = new CharacterData[128]; this.texture = texture; + this.fontIndex = fontIndex; + } + + public GameFont(String name, int fontSize, CharacterData[] characterDatas, long fontIndex) { + this.name = name; + this.fontSize = fontSize; + this.characterDatas = characterDatas; + this.texture = null; + this.fontIndex = fontIndex; } public String getName() { @@ -28,12 +44,22 @@ public CharacterData[] getCharacterDatas() { return characterDatas; } + public void characterDatas(CharacterData[] characterDatas) { + this.characterDatas = characterDatas; + } + public Texture texture() { return texture; } public void delete() { - texture.delete(); + if (texture != null) { + texture.delete(); + } + } + + public static long getFontIndex() { + return FONT_INDEX++; } }