diff --git a/build.gradle b/build.gradle index a4ab7b5..b306e64 100644 --- a/build.gradle +++ b/build.gradle @@ -63,6 +63,7 @@ dependencies { implementation "org.lwjgl:lwjgl-stb" implementation "org.lwjgl:lwjgl-nfd" implementation "org.lwjgl:lwjgl-freetype" + implementation "org.lwjgl:lwjgl-harfbuzz" implementation "org.lwjgl:lwjgl-nanovg" implementation "org.lwjgl:lwjgl::$lwjglNatives" implementation "org.lwjgl:lwjgl-glfw::$lwjglNatives" @@ -72,6 +73,7 @@ dependencies { implementation "org.lwjgl:lwjgl-stb::$lwjglNatives" implementation "org.lwjgl:lwjgl-nfd::$lwjglNatives" implementation "org.lwjgl:lwjgl-freetype::$lwjglNatives" + implementation "org.lwjgl:lwjgl-harfbuzz::$lwjglNatives" implementation "org.lwjgl:lwjgl-nanovg::$lwjglNatives" @@ -81,4 +83,4 @@ dependencies { //Primitive Collections implementation 'de.speiger:Primitive-Collections:0.9.0' -} \ No newline at end of file +} diff --git a/src/main/java/speiger/src/coreengine/NewInputTest.java b/src/main/java/speiger/src/coreengine/NewInputTest.java index 90bbdc0..bf5233b 100644 --- a/src/main/java/speiger/src/coreengine/NewInputTest.java +++ b/src/main/java/speiger/src/coreengine/NewInputTest.java @@ -6,6 +6,8 @@ import java.util.function.BiConsumer; import org.lwjgl.glfw.GLFW; import org.lwjgl.opengl.GL; import org.lwjgl.opengl.GL11; +import org.lwjgl.system.Configuration; +import org.lwjgl.util.freetype.FreeType; import speiger.src.collections.objects.lists.ObjectArrayList; import speiger.src.coreengine.assets.AssetLocation; @@ -16,8 +18,8 @@ import speiger.src.coreengine.math.vector.matrix.Matrix4f; import speiger.src.coreengine.rendering.gui.font.Font; import speiger.src.coreengine.rendering.gui.font.FontManager; import speiger.src.coreengine.rendering.gui.font.glyth.Glyth; -import speiger.src.coreengine.rendering.gui.font.glyth.GlythData; -import speiger.src.coreengine.rendering.gui.font.glyth.GlythData.GlythBaker; +import speiger.src.coreengine.rendering.gui.font.glyth.UnbakedGlyth; +import speiger.src.coreengine.rendering.gui.font.glyth.UnbakedGlyth.GlythBaker; import speiger.src.coreengine.rendering.gui.font.glyth.IGlythSheetInfo; import speiger.src.coreengine.rendering.gui.font.glyth.MissingGlyth; import speiger.src.coreengine.rendering.gui.font.providers.IFontProvider; @@ -62,6 +64,7 @@ public class NewInputTest { } public void run() { + Configuration.HARFBUZZ_LIBRARY_NAME.set(FreeType.getLibrary()); GLFW.glfwInit(); manager.initialize(); Mouse.INSTANCE.init(bus); @@ -69,7 +72,7 @@ public class NewInputTest { Joystick.INSTANCE.init(manager, bus); FileDrop.INSTANCE.init(bus); manager.addDevices(Mouse.INSTANCE, Keyboard.INSTANCE, Joystick.INSTANCE, FileDrop.INSTANCE); - Window window = manager.builder().title("Testing Engine").antialis(16).build(); + Window window = manager.builder().title("Testing Engine").antialis(1).build(); shaderTest.register(); guiShader.register(); assets.addListener(GLStateTracker.instance().shaders); @@ -82,7 +85,6 @@ public class NewInputTest { IFontProvider provider = STBTrueTypeProvider.create(AssetLocation.of("font/roboto/font.json"), assets); guiShader.get().proView.set(new Matrix4f().ortho(0, 0, window.width(), window.height(), 1000, -1000)); - guiShader.get().model.set(new Matrix4f().scale(1.1F)); int size = 512; int half = size >> 1; @@ -109,7 +111,7 @@ public class NewInputTest { T.cancel(); System.out.println("Testing"); // texture.bind(); - GlythData data = provider.glythData("C".codePointAt(0), 0, 18.5F, 2F); + UnbakedGlyth data = provider.glythData("C".codePointAt(0), 0, 18.5F, 2F); data.bake(new GlythBaker() { @Override public Glyth bake(IGlythSheetInfo info) { @@ -145,27 +147,28 @@ public class NewInputTest { builder.pos(-0.5F, 0.5F, 0).tex(0F, 0F).rgba(-1).endVertex(); builder.pos(-0.5F, -0.5F, 0).tex(0F, 1F).rgba(-1).endVertex(); - Font font = fonts.createFont(18F, 4F); + Font font = fonts.createFont(18F, 1F); TestModel model = new TestModel(builder.getBytes()); TestModel[] guiModel = new TestModel[1]; List draws = new ObjectArrayList<>(); - font.drawText("Testing my Text", 50, 50, -1, new TexturedBuffer((K, V) -> { + font.drawText("The Quick brown fox Jumps over the Lazy dog", 50, 50, -1, new TexturedBuffer((K, V) -> { draws.addAll(K); guiModel[0] = new TestModel(V); System.out.println("Testing: "+V.length+" bytes, "+K.size()); })); + GLStateTracker tracker = GLStateTracker.instance(); GL11.glClearColor(0.2F, 0.55F, 0.66F, 1F); while(!window.shouldClose()) { GLFW.glfwPollEvents(); window.beginFrame(); GL11.glClear(GL11.GL_COLOR_BUFFER_BIT); -// texture.bind(); -// shaderTest.bind(); -// model.bindArray(); -// GLStateTracker.drawArrays(GLMode.TRIANGLES.glValue(), 6); -// model.unbindArray(); + texture.bind(); + shaderTest.bind(); + model.bindArray(); + GLStateTracker.drawArrays(GLMode.TRIANGLES.glValue(), 6); + model.unbindArray(); guiModel[0].bindArray(); guiShader.bind(); for(GLDraw draw : draws) { @@ -206,7 +209,6 @@ public class NewInputTest { draws.add(new GLDraw(previousId, lastVertex, count)); lastVertex+=count; previousId = textureId; - System.out.println("Texture: "+textureId); } return builder; } @@ -228,7 +230,6 @@ public class NewInputTest { public static class GuiShader extends SimpleShader { public TextureUniform texture = uniforms.addTexture("texture", 0); public Matrix4fUniform proView = uniforms.addMat("proViewMatrix", new Matrix4f()); - public Matrix4fUniform model = uniforms.addMat("modelmatrix", new Matrix4f()); public GuiShader(IAssetProvider provider) { super(provider, "gui_shader", AssetLocation.of("shader/testGui/vertex.vs"), AssetLocation.of("shader/testGui/fragment.fs"), "in_position", "in_tex", "in_color"); diff --git a/src/main/java/speiger/src/coreengine/rendering/gui/font/Font.java b/src/main/java/speiger/src/coreengine/rendering/gui/font/Font.java index 62496de..eba4942 100644 --- a/src/main/java/speiger/src/coreengine/rendering/gui/font/Font.java +++ b/src/main/java/speiger/src/coreengine/rendering/gui/font/Font.java @@ -6,8 +6,8 @@ import java.util.function.Consumer; import speiger.src.coreengine.assets.AssetLocation; import speiger.src.coreengine.rendering.gui.font.glyth.Glyth; import speiger.src.coreengine.rendering.gui.font.glyth.GlythData; -import speiger.src.coreengine.rendering.gui.font.glyth.GlythData.GlythBaker; import speiger.src.coreengine.rendering.gui.font.glyth.MissingGlyth; +import speiger.src.coreengine.rendering.gui.font.glyth.UnbakedGlyth.GlythBaker; import speiger.src.coreengine.rendering.tesselation.buffer.IVertexBuilder; public class Font { @@ -19,6 +19,7 @@ public class Font { float oversample; GlythCache[] styledCache = new GlythCache[] {new GlythCache(this, 0), new GlythCache(this, 1), new GlythCache(this, 2), new GlythCache(this, 3)}; MissingGlyth missingGlyth; + GlythData missingData; protected Font(Map fonts, GlythBaker baker, float size, float oversample, Consumer clearing) { this.fonts = fonts; @@ -26,6 +27,7 @@ public class Font { this.size = size; this.oversample = oversample; this.missingGlyth = new MissingGlyth(size, oversample * 2F); + missingData = new GlythData(missingGlyth); clearing.accept(this::reset); } @@ -34,6 +36,7 @@ public class Font { styledCache[i].reset(); } missingGlyth.cleanCache(); + missingData.cleanCache(); } public GlythData data(AssetLocation font, int codepoint, int style) { @@ -54,7 +57,7 @@ public class Font { int codepoint = text.codePointAt(i); GlythData data = data(DEFAULT, codepoint, 0); if(previousCodepoint != -1) { - x += data.kernling(previousCodepoint); + x -= data.kerning(previousCodepoint); } Glyth glyth = glyth(DEFAULT, codepoint, 0); if(glyth.isValid()) { @@ -63,8 +66,6 @@ public class Font { float maxX = glyth.right() + x; float maxY = glyth.bottom() + y; -// System.out.println("Test: MinX="+minX+", MinY="+minY+", MaxX="+maxX+", MaxY="+maxY+", MinU="+glyth.minU()+", MinV="+glyth.minV()+", MaxU="+glyth.maxU()+", MaxV="+glyth.maxV()); - IVertexBuilder builder = buffer.builderForTexture(glyth.texture()); builder.pos(minX, minY, 0F).tex(glyth.minU(), glyth.minV()).rgba(-1).endVertex(); builder.pos(maxX, minY, 0F).tex(glyth.maxU(), glyth.minV()).rgba(-1).endVertex(); diff --git a/src/main/java/speiger/src/coreengine/rendering/gui/font/FontGroup.java b/src/main/java/speiger/src/coreengine/rendering/gui/font/FontGroup.java index b3288d2..dd09483 100644 --- a/src/main/java/speiger/src/coreengine/rendering/gui/font/FontGroup.java +++ b/src/main/java/speiger/src/coreengine/rendering/gui/font/FontGroup.java @@ -3,7 +3,7 @@ package speiger.src.coreengine.rendering.gui.font; import java.util.List; import speiger.src.coreengine.assets.AssetLocation; -import speiger.src.coreengine.rendering.gui.font.glyth.GlythData; +import speiger.src.coreengine.rendering.gui.font.glyth.UnbakedGlyth; import speiger.src.coreengine.rendering.gui.font.providers.IFontProvider; public class FontGroup { @@ -15,9 +15,9 @@ public class FontGroup { this.providers = providers; } - public GlythData data(int codepoint, int style, float size, float oversample) { + public UnbakedGlyth data(int codepoint, int style, float size, float oversample) { for(int i = 0,m=providers.size();i>> { - private static final int TEXTURE_SIZE = 512; + private static final int TEXTURE_SIZE = 4096; private static final AssetFilter FILTER = AssetFilter.json("font"); Map fonts = Object2ObjectMap.builder().linkedMap(); Map> fontParsers = Object2ObjectMap.builder().map(); @@ -32,6 +33,7 @@ public class FontManager extends SteppedReloadableAsset parser) { diff --git a/src/main/java/speiger/src/coreengine/rendering/gui/font/FontTexture.java b/src/main/java/speiger/src/coreengine/rendering/gui/font/FontTexture.java index 00ab193..c681a31 100644 --- a/src/main/java/speiger/src/coreengine/rendering/gui/font/FontTexture.java +++ b/src/main/java/speiger/src/coreengine/rendering/gui/font/FontTexture.java @@ -63,7 +63,6 @@ public class FontTexture extends BaseTexture { float maxU = (float)((result.x) + info.width()) / (float)bounds; float minV = (float)(result.y) / (float)bounds; float maxV = (float)((result.y) + info.height()) / (float)bounds; - System.out.println("Baking: MinU="+minU+", MinV="+minV+", MaxU="+maxU+", MaxV="+maxV+", "+result.x+", "+result.y); return new Glyth(id(), minU, minV, maxU, maxV, info.left(), info.right(), info.top(), info.bottom()); } return null; diff --git a/src/main/java/speiger/src/coreengine/rendering/gui/font/GlythCache.java b/src/main/java/speiger/src/coreengine/rendering/gui/font/GlythCache.java index fb22c36..4c4d14a 100644 --- a/src/main/java/speiger/src/coreengine/rendering/gui/font/GlythCache.java +++ b/src/main/java/speiger/src/coreengine/rendering/gui/font/GlythCache.java @@ -8,6 +8,7 @@ import speiger.src.collections.objects.maps.impl.hash.Object2ObjectOpenHashMap; import speiger.src.coreengine.assets.AssetLocation; import speiger.src.coreengine.rendering.gui.font.glyth.Glyth; import speiger.src.coreengine.rendering.gui.font.glyth.GlythData; +import speiger.src.coreengine.rendering.gui.font.glyth.UnbakedGlyth; public class GlythCache { private Font font; @@ -53,12 +54,12 @@ public class GlythCache { } private GlythData compute(int codepoint) { - GlythData data = font.font(fontId).data(codepoint, style, font.size, font.oversample); - return data == null ? font.missingGlyth : data; + UnbakedGlyth data = font.font(fontId).data(codepoint, style, font.size, font.oversample); + return data == null ? font.missingData : new GlythData(data); } private Glyth bake(int codepoint) { - return data(codepoint).bake(font.baker); + return data(codepoint).unbaked().bake(font.baker); } } } \ No newline at end of file diff --git a/src/main/java/speiger/src/coreengine/rendering/gui/font/glyth/GlythData.java b/src/main/java/speiger/src/coreengine/rendering/gui/font/glyth/GlythData.java index 8385c93..eab73f2 100644 --- a/src/main/java/speiger/src/coreengine/rendering/gui/font/glyth/GlythData.java +++ b/src/main/java/speiger/src/coreengine/rendering/gui/font/glyth/GlythData.java @@ -1,20 +1,19 @@ package speiger.src.coreengine.rendering.gui.font.glyth; -public interface GlythData { - public float advance(); - public float kernling(int codepoint); - public default float shadowOffset() { return 1F; } - public Glyth bake(GlythBaker baker); +import speiger.src.collections.ints.maps.impl.hash.Int2FloatOpenHashMap; +import speiger.src.collections.ints.maps.interfaces.Int2FloatMap; + +public class GlythData { + UnbakedGlyth data; + Int2FloatMap kernings = new Int2FloatOpenHashMap(); - - public static record EmptyGlythData(float advance) implements GlythData { - @Override - public Glyth bake(GlythBaker baker) { return Glyth.EMPTY; } - @Override - public float kernling(int codepoint) { return 0F; } + public GlythData(UnbakedGlyth data) { + this.data = data; } - public static interface GlythBaker { - Glyth bake(IGlythSheetInfo info); - } -} \ No newline at end of file + public float advance() { return data.advance(); } + public float shadowOffset() { return data.shadowOffset(); } + public float kerning(int codepoint) { return kernings.computeFloatIfAbsent(codepoint, data::kerning); } + public UnbakedGlyth unbaked() { return data; } + public void cleanCache() { kernings.clear(); } +} diff --git a/src/main/java/speiger/src/coreengine/rendering/gui/font/glyth/GlythDataOld.java b/src/main/java/speiger/src/coreengine/rendering/gui/font/glyth/GlythDataOld.java deleted file mode 100644 index f9c921c..0000000 --- a/src/main/java/speiger/src/coreengine/rendering/gui/font/glyth/GlythDataOld.java +++ /dev/null @@ -1,26 +0,0 @@ -package speiger.src.coreengine.rendering.gui.font.glyth; - -import speiger.src.collections.ints.functions.function.Int2FloatFunction; -import speiger.src.collections.ints.maps.impl.hash.Int2FloatOpenHashMap; -import speiger.src.collections.ints.maps.interfaces.Int2FloatMap; - -public class GlythDataOld { - float advance; - float shadowOffset; - Int2FloatMap kernlings = new Int2FloatOpenHashMap(); - Int2FloatFunction kernling; - - public GlythDataOld(float advance, float shadowOffset, Int2FloatFunction kernling) { - this.advance = advance; - this.shadowOffset = shadowOffset; - this.kernling = kernling; - } - - public GlythDataOld(GlythData glyth) { - this(glyth.advance(), glyth.shadowOffset(), glyth::kernling); - } - - public float advance() { return advance; } - public float shadowOffset() { return shadowOffset; } - public float kernling(int codepoint) { return kernlings.computeFloatIfAbsent(codepoint, kernling); } -} diff --git a/src/main/java/speiger/src/coreengine/rendering/gui/font/glyth/MissingGlyth.java b/src/main/java/speiger/src/coreengine/rendering/gui/font/glyth/MissingGlyth.java index f13c104..12557cb 100644 --- a/src/main/java/speiger/src/coreengine/rendering/gui/font/glyth/MissingGlyth.java +++ b/src/main/java/speiger/src/coreengine/rendering/gui/font/glyth/MissingGlyth.java @@ -4,7 +4,7 @@ import speiger.src.coreengine.math.MathUtils; import speiger.src.coreengine.rendering.gui.font.FontTexture; import speiger.src.coreengine.rendering.textures.custom.Drawable; -public class MissingGlyth implements GlythData { +public class MissingGlyth implements UnbakedGlyth { private static final int WIDTH = 10; private static final int HEIGHT = 57; float size; @@ -31,7 +31,7 @@ public class MissingGlyth implements GlythData { @Override public float advance() { return advance; } @Override - public float kernling(int codepoint) { return 0; } + public float kerning(int codepoint) { return 0; } @Override public Glyth bake(GlythBaker baker) { if(cached == null) { diff --git a/src/main/java/speiger/src/coreengine/rendering/gui/font/glyth/UnbakedGlyth.java b/src/main/java/speiger/src/coreengine/rendering/gui/font/glyth/UnbakedGlyth.java new file mode 100644 index 0000000..dde358c --- /dev/null +++ b/src/main/java/speiger/src/coreengine/rendering/gui/font/glyth/UnbakedGlyth.java @@ -0,0 +1,20 @@ +package speiger.src.coreengine.rendering.gui.font.glyth; + +public interface UnbakedGlyth { + public float advance(); + public float kerning(int codepoint); + public default float shadowOffset() { return 1F; } + public Glyth bake(GlythBaker baker); + + + public static record EmptyGlythData(float advance) implements UnbakedGlyth { + @Override + public Glyth bake(GlythBaker baker) { return Glyth.EMPTY; } + @Override + public float kerning(int codepoint) { return 0F; } + } + + public static interface GlythBaker { + Glyth bake(IGlythSheetInfo info); + } +} \ No newline at end of file diff --git a/src/main/java/speiger/src/coreengine/rendering/gui/font/providers/FreeTypeProvider.java b/src/main/java/speiger/src/coreengine/rendering/gui/font/providers/FreeTypeProvider.java index 70611f1..4f7f059 100644 --- a/src/main/java/speiger/src/coreengine/rendering/gui/font/providers/FreeTypeProvider.java +++ b/src/main/java/speiger/src/coreengine/rendering/gui/font/providers/FreeTypeProvider.java @@ -1,23 +1,297 @@ package speiger.src.coreengine.rendering.gui.font.providers; -import org.lwjgl.util.freetype.FreeType; +import java.nio.ByteBuffer; -import speiger.src.coreengine.rendering.gui.font.glyth.GlythData; +import org.lwjgl.PointerBuffer; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.system.MemoryUtil; +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.FT_Vector; +import org.lwjgl.util.freetype.FreeType; +import org.lwjgl.util.harfbuzz.HarfBuzz; +import org.lwjgl.util.harfbuzz.hb_glyph_position_t; + +import com.google.gson.JsonObject; + +import speiger.src.collections.ints.sets.IntOpenHashSet; +import speiger.src.collections.ints.sets.IntSet; +import speiger.src.collections.longs.misc.pairs.LongObjectPair; +import speiger.src.coreengine.assets.AssetLocation; +import speiger.src.coreengine.assets.base.IAsset; +import speiger.src.coreengine.assets.base.IAssetProvider; +import speiger.src.coreengine.assets.parsers.NativeMemoryParser; +import speiger.src.coreengine.rendering.gui.font.FontTexture; +import speiger.src.coreengine.rendering.gui.font.glyth.Glyth; +import speiger.src.coreengine.rendering.gui.font.glyth.IGlythSheetInfo; +import speiger.src.coreengine.rendering.gui.font.glyth.UnbakedGlyth; +import speiger.src.coreengine.rendering.gui.font.glyth.UnbakedGlyth.EmptyGlythData; +import speiger.src.coreengine.rendering.textures.custom.Drawable; +import speiger.src.coreengine.utils.helpers.JsonUtil; public class FreeTypeProvider implements IFontProvider { - public void test() { - + FreeTypeInstance[] instance; + + public FreeTypeProvider(FreeTypeInstance[] instance) { + this.instance = instance; + } + + public static IFontProvider create(AssetLocation location, IAssetProvider provider) { + try(IAsset asset = provider.getAsset(location)) { + return load(asset.json(), provider); + } + catch(Exception e) { e.printStackTrace(); } + return null; + } + + public static IFontProvider load(JsonObject data, IAssetProvider provider) { + long library = FreeTypeLibrary.get(); + if(library == 0L) return null; + FreeTypeInstance[] instances = new FreeTypeInstance[4]; + instances[0] = create(library, 0, data.getAsJsonObject("regular"), provider); + if(instances[0] == null) return null; + instances[1] = create(library, 1, data.getAsJsonObject("bold"), provider); + instances[2] = create(library, 2, data.getAsJsonObject("italic"), provider); + instances[3] = create(library, 3, data.getAsJsonObject("bold_italic"), provider); + return new FreeTypeProvider(instances); + } + + private static FreeTypeInstance create(long library, int style, JsonObject obj, IAssetProvider provider) { + if(obj == null || !obj.has("file")) return null; + AssetLocation location = AssetLocation.of(obj.get("file").getAsString()); + float oversample = JsonUtil.getOrDefault(obj, "oversample", 1F); + float shadowOffset = JsonUtil.getOrDefault(obj, "shadowOffset", 1F); + float xOff = 0; + float yOff = 0; + StringBuilder builder = new StringBuilder(); + JsonObject shift = obj.getAsJsonObject("offset"); + if(shift != null) { + xOff = JsonUtil.getOrDefault(shift, "x", 0F); + yOff = JsonUtil.getOrDefault(shift, "y", 0F); + } + JsonUtil.iterateValues(obj.get("skip"), T -> builder.append(T.getAsString())); + LongObjectPair value = parse(location, provider, library); + if(value == null) return null; + return new FreeTypeInstance(style, value.getLongKey(), value.getValue(), oversample, xOff, yOff, shadowOffset, builder.toString()); + } + + private static LongObjectPair parse(AssetLocation location, IAssetProvider provider, long library) { + try(IAsset asset = provider.getAsset(location); MemoryStack stack = MemoryStack.stackPush()) { + ByteBuffer buffer = asset.custom(NativeMemoryParser.INSTANCE); + PointerBuffer facePointer = stack.mallocPointer(1); + if(FreeTypeLibrary.parseError(FreeType.FT_New_Memory_Face(library, buffer, 0L, facePointer), "Creating Font Face")) { + MemoryUtil.memFree(buffer); + return null; + } + FT_Face face = FT_Face.create(facePointer.get()); + String s = FreeType.FT_Get_Font_Format(face); + if(!"TrueType".equals(s)) { + MemoryUtil.memFree(buffer); + throw new IllegalStateException("Font type ["+s+"] is not true type"); + } + if(FreeTypeLibrary.parseError(FreeType.FT_Select_Charmap(face, FreeType.FT_ENCODING_UNICODE), "Applying Unicode Encoding")) { + MemoryUtil.memFree(buffer); + return null; + } + return LongObjectPair.of(MemoryUtil.memAddress(buffer), face); + } + catch(Exception exception) { + exception.printStackTrace(); + return null; + } } @Override - public GlythData glythData(int codepoint, int style, float size, float oversample) { - return null; + public UnbakedGlyth glythData(int codepoint, int style, float size, float oversample) { + return instance[style & 0x3].gylthData(codepoint, size, oversample); } @Override public void close() { + for(int i = 0;i<4;i++) { + if(instance[i] != null) { + instance[i].free(); + } + } + instance = null; + } + + public static class FreeTypeGlyth implements UnbakedGlyth { + final FT_Face face; + final int width; + final int height; + final float xOff; + final float yOff; + final float oversample; + private final float advance; + final int glyth; + final int glythCodepoint; + final long harfBuzzFont; + + public FreeTypeGlyth(FT_Face face, long harfBuzzFont, float xOff, float yOff, int width, int height, float advance, float oversample, int glyth, int glythCodepoint) { + this.face = face; + this.harfBuzzFont = harfBuzzFont; + this.width = width; + this.height = height; + this.oversample = oversample; + this.advance = advance / oversample; + this.xOff = xOff / oversample; + this.yOff = yOff / oversample; + this.glyth = glyth; + this.glythCodepoint = glythCodepoint; + } + + @Override + public float advance() { + return advance; + } + + @Override + public float kerning(int codepoint) { + int index = FreeType.FT_Get_Char_Index(face, codepoint); + if(index == 0) return 0; + StringBuilder builder = new StringBuilder(); + builder.append(Character.toChars(codepoint)); + builder.append(Character.toChars(glythCodepoint)); + float hbresult = (getAdvance(Character.toString(codepoint)) - getAdvance(builder.toString())) / 64F; + return hbresult; + } + + private float getAdvance(String adv) { + long id = HarfBuzz.hb_buffer_create(); + try { + HarfBuzz.hb_buffer_add_utf8(id, adv, 0, adv.length()); + HarfBuzz.hb_buffer_guess_segment_properties(id); + HarfBuzz.hb_shape(harfBuzzFont, id, null); + hb_glyph_position_t.Buffer positions = HarfBuzz.hb_buffer_get_glyph_positions(id); + float result = positions.hasRemaining() ? positions.x_advance() : 0F; + HarfBuzz.hb_buffer_destroy(positions.address()); + return result; + } + finally { + HarfBuzz.hb_buffer_destroy(id); + } + } + + @Override + public Glyth bake(GlythBaker baker) { + return baker.bake(new IGlythSheetInfo() { + @Override + public float xOffset() { return xOff; } + @Override + public float yOffset() { return yOff; } + @Override + public int width() { return width; } + @Override + public int height() { return height; } + @Override + public float oversample() { return oversample; } + @Override + public void upload(int texture, int x, int y) { + Drawable drawable = new Drawable(FontTexture.formatByColor(false), width, height); + if(drawable.drawFont(face, glyth)) { + drawable.upload(texture, x, y, 0, 0, width, height); + } + drawable.close(); + } + + @Override + public boolean isColored() { return false; } + }); + } } + public static class FreeTypeInstance { + final int style; + long data; + final FT_Face face; + IntSet skip = new IntOpenHashSet(); + final float oversample; + final float xOff; + final float yOff; + final float shadowOffset; + final long harfBuzzFont; + + public FreeTypeInstance(int style, long data, FT_Face face, float oversample, float xOff, float yOff, float shadowOffset, String skip) { + this.style = style; + this.data = data; + this.face = face; + this.harfBuzzFont = HarfBuzz.hb_ft_font_create_referenced(face.address()); + skip.codePoints().forEach(this.skip::add); + this.oversample = oversample; + this.xOff = xOff; + this.yOff = yOff; + this.shadowOffset = shadowOffset; + try(MemoryStack stack = MemoryStack.stackPush()) { + FT_Vector ft_vector = FT_Vector.malloc(stack).set(Math.round(oversample * xOff * 64F), Math.round(oversample * -yOff * 64F)); + FreeType.FT_Set_Transform(face, null, ft_vector); + } + } + + public void free() { + if(data == 0L) return; + FreeType.FT_Done_Face(face); + MemoryUtil.nmemFree(data); + data = 0L; + } + + public UnbakedGlyth gylthData(int codepoint, float size, float oversample) { + if(skip.contains(codepoint)) return null; + int index = FreeType.FT_Get_Char_Index(face, codepoint); + if(index == 0) return null; + oversample *= this.oversample; + int pixels = Math.round(size * oversample); + if(FreeTypeLibrary.parseError(FreeType.FT_Set_Pixel_Sizes(face, pixels, pixels), "Set Pixel Size")) return null; + + if(FreeTypeLibrary.parseError(FreeType.FT_Load_Glyph(face, index, FreeType.FT_LOAD_NO_BITMAP | FreeType.FT_LOAD_BITMAP_METRICS_ONLY), "Loading Glyth")) return null; + FT_GlyphSlot slot = face.glyph(); + if(slot == null) { + System.out.println("Glyth didn't load for some reason"); + return null; + } + float advance = slot.advance().x() / 64F; + FT_Bitmap bitmap = slot.bitmap(); + + int left = slot.bitmap_left(); + int top = slot.bitmap_top(); + int width = bitmap.width(); + int height = bitmap.rows(); + if(width > 0 && height > 0) return new FreeTypeGlyth(face, harfBuzzFont, left, -top, width, height, advance, oversample, index, codepoint); + return new EmptyGlythData(advance / oversample); + } + } + + public static class FreeTypeLibrary { + private static long pointer = 0L; + + public static long get() { + if(pointer == 0L) { + try(MemoryStack stack = MemoryStack.stackPush()) { + PointerBuffer pointBuffer = stack.callocPointer(1); + int result = FreeType.FT_Init_FreeType(pointBuffer); + if(result != 0) { + throw new IllegalStateException(FreeType.FT_Error_String(result)); + } + pointer = pointBuffer.get(); + } + } + return pointer; + } + + public static boolean parseError(int result, String action) { + if(result == 0) return false; + String error = FreeType.FT_Error_String(result); + System.out.println("Couldn't do ["+action+"] because of "+error); + return true; + } + + public static void close() { + if(pointer == 0L) return; + FreeType.FT_Done_Library(pointer); + pointer = 0L; + } + } } diff --git a/src/main/java/speiger/src/coreengine/rendering/gui/font/providers/IFontProvider.java b/src/main/java/speiger/src/coreengine/rendering/gui/font/providers/IFontProvider.java index 7e38696..f63ad7c 100644 --- a/src/main/java/speiger/src/coreengine/rendering/gui/font/providers/IFontProvider.java +++ b/src/main/java/speiger/src/coreengine/rendering/gui/font/providers/IFontProvider.java @@ -1,6 +1,6 @@ package speiger.src.coreengine.rendering.gui.font.providers; -import speiger.src.coreengine.rendering.gui.font.glyth.GlythData; +import speiger.src.coreengine.rendering.gui.font.glyth.UnbakedGlyth; public interface IFontProvider { public static final int REGULAR = 0; @@ -9,6 +9,6 @@ public interface IFontProvider { public static final int ITALIC_BOLD = 3; - public GlythData glythData(int codepoint, int style, float size, float oversample); + public UnbakedGlyth glythData(int codepoint, int style, float size, float oversample); public void close(); } diff --git a/src/main/java/speiger/src/coreengine/rendering/gui/font/providers/STBTrueTypeProvider.java b/src/main/java/speiger/src/coreengine/rendering/gui/font/providers/STBTrueTypeProvider.java index 5684deb..d693a67 100644 --- a/src/main/java/speiger/src/coreengine/rendering/gui/font/providers/STBTrueTypeProvider.java +++ b/src/main/java/speiger/src/coreengine/rendering/gui/font/providers/STBTrueTypeProvider.java @@ -18,8 +18,8 @@ import speiger.src.coreengine.assets.base.IAssetProvider; import speiger.src.coreengine.assets.parsers.NativeMemoryParser; import speiger.src.coreengine.rendering.gui.font.FontTexture; import speiger.src.coreengine.rendering.gui.font.glyth.Glyth; -import speiger.src.coreengine.rendering.gui.font.glyth.GlythData; -import speiger.src.coreengine.rendering.gui.font.glyth.GlythData.EmptyGlythData; +import speiger.src.coreengine.rendering.gui.font.glyth.UnbakedGlyth; +import speiger.src.coreengine.rendering.gui.font.glyth.UnbakedGlyth.EmptyGlythData; import speiger.src.coreengine.rendering.gui.font.glyth.IGlythSheetInfo; import speiger.src.coreengine.rendering.textures.custom.Drawable; import speiger.src.coreengine.utils.helpers.JsonUtil; @@ -112,7 +112,7 @@ public class STBTrueTypeProvider implements IFontProvider { data = 0L; } - public GlythData glythData(int codepoint, float size, float oversample) { + public UnbakedGlyth glythData(int codepoint, float size, float oversample) { if(skip.contains(codepoint)) return null; int glyth = STBTruetype.nstbtt_FindGlyphIndex(info.address(), codepoint); if(glyth == 0) return null; @@ -142,7 +142,7 @@ public class STBTrueTypeProvider implements IFontProvider { } @Override - public GlythData glythData(int codepoint, int style, float size, float oversample) { + public UnbakedGlyth glythData(int codepoint, int style, float size, float oversample) { TrueTypeInstance instance = instances[style & 0x3]; return instance == null ? null : instance.glythData(codepoint, size, oversample); } @@ -157,7 +157,7 @@ public class STBTrueTypeProvider implements IFontProvider { instances = null; } - private static class STBGlyth implements GlythData { + private static class STBGlyth implements UnbakedGlyth { final TrueTypeInstance owner; final float xOffset; final float yOffset; @@ -185,7 +185,7 @@ public class STBTrueTypeProvider implements IFontProvider { @Override public float shadowOffset() { return owner.shadowOffset; } @Override - public float kernling(int codepoint) { return STBTruetype.stbtt_GetCodepointKernAdvance(owner.info, codepoint, glyth) * scale / oversample; } + public float kerning(int codepoint) { return STBTruetype.stbtt_GetCodepointKernAdvance(owner.info, codepoint, glyth) * scale / oversample; } @Override public Glyth bake(GlythBaker baker) { return baker.bake(new IGlythSheetInfo() { diff --git a/src/main/java/speiger/src/coreengine/rendering/textures/custom/Drawable.java b/src/main/java/speiger/src/coreengine/rendering/textures/custom/Drawable.java index 160d420..0fd6d94 100644 --- a/src/main/java/speiger/src/coreengine/rendering/textures/custom/Drawable.java +++ b/src/main/java/speiger/src/coreengine/rendering/textures/custom/Drawable.java @@ -1,9 +1,16 @@ package speiger.src.coreengine.rendering.textures.custom; +import java.nio.ByteBuffer; +import java.util.Objects; + import org.lwjgl.stb.STBImage; import org.lwjgl.stb.STBTTFontinfo; import org.lwjgl.stb.STBTruetype; import org.lwjgl.system.MemoryUtil; +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 speiger.src.coreengine.math.misc.ColorSpaces; import speiger.src.coreengine.rendering.utils.GLFunctions; @@ -64,6 +71,21 @@ public class Drawable implements IDrawable, AutoCloseable { if(pixels == 0L) throw new IllegalStateException("Pixel Data doesn't exist"); } + public boolean drawFont(FT_Face face, int glyth) { + ensureValid(0); + if(components != 1) throw new IllegalStateException("Format has to be 1 component"); + if(FreeType.FT_Load_Glyph(face, glyth, 4) != 0) return false; + + FT_GlyphSlot slot = Objects.requireNonNull(face.glyph()); + FT_Bitmap map = slot.bitmap(); + if(map.pixel_mode() != FreeType.FT_PIXEL_MODE_GRAY) throw new IllegalStateException("Pixel isn't a grayscale picture"); + if(map.width() != width() || map.rows() != height()) throw new IllegalStateException("Bounds do not match"); + int size = map.width() * map.rows(); + ByteBuffer buffer = Objects.requireNonNull(map.buffer(size)); + MemoryUtil.memCopy(MemoryUtil.memAddress(buffer), pixels(), size); + return true; + } + public void drawFont(STBTTFontinfo info, int glyth, float sourceX, float sourceY, int targetX, int targetY, int width, int height, float scaleX, float scaleY) { ensureValid(targetX, targetY); ensureValid(targetX + width, targetY + height); diff --git a/src/main/resources/assets/base/font/roboto/font.json b/src/main/resources/assets/base/font/roboto/font.json index a212135..335c4ed 100644 --- a/src/main/resources/assets/base/font/roboto/font.json +++ b/src/main/resources/assets/base/font/roboto/font.json @@ -1,8 +1,8 @@ { - "type": "stb-ttf", + "type": "free-ttf", "regular": { "file": "font/roboto/Roboto-Medium.ttf", - "oversample": 1, + "oversample": 3, "shadowOffset": 1, "skip": "", "offset": { "x": 0, "y": 0 } diff --git a/src/main/resources/assets/base/shader/testGui/vertex.vs b/src/main/resources/assets/base/shader/testGui/vertex.vs index d6f9acd..743718a 100644 --- a/src/main/resources/assets/base/shader/testGui/vertex.vs +++ b/src/main/resources/assets/base/shader/testGui/vertex.vs @@ -8,7 +8,6 @@ out vec4 pass_color; out vec2 pass_tex; uniform mat4 proViewMatrix; -uniform mat4 modelmatrix; void main() {