Finishing up the fonts \o/
This commit is contained in:
parent
5fd1ff4904
commit
96d3c5b345
|
@ -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"
|
||||
|
||||
|
||||
|
|
|
@ -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<GLDraw> 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");
|
||||
|
|
|
@ -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<AssetLocation, FontGroup> fonts, GlythBaker baker, float size, float oversample, Consumer<Runnable> 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();
|
||||
|
|
|
@ -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<m;i++) {
|
||||
GlythData data = providers.get(i).glythData(codepoint, style, size, oversample);
|
||||
UnbakedGlyth data = providers.get(i).glythData(codepoint, style, size, oversample);
|
||||
if(data != null) return data;
|
||||
}
|
||||
return null;
|
||||
|
|
|
@ -18,12 +18,13 @@ import speiger.src.coreengine.assets.base.MultiAsset;
|
|||
import speiger.src.coreengine.assets.base.SteppedReloadableAsset;
|
||||
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.providers.FreeTypeProvider;
|
||||
import speiger.src.coreengine.rendering.gui.font.providers.IFontProvider;
|
||||
import speiger.src.coreengine.rendering.gui.font.providers.STBTrueTypeProvider;
|
||||
import speiger.src.coreengine.utils.helpers.JsonUtil;
|
||||
|
||||
public class FontManager extends SteppedReloadableAsset<Map<AssetLocation, ObjectList<IFontProvider>>> {
|
||||
private static final int TEXTURE_SIZE = 512;
|
||||
private static final int TEXTURE_SIZE = 4096;
|
||||
private static final AssetFilter FILTER = AssetFilter.json("font");
|
||||
Map<AssetLocation, FontGroup> fonts = Object2ObjectMap.builder().linkedMap();
|
||||
Map<String, BiFunction<JsonObject, IAssetProvider, IFontProvider>> fontParsers = Object2ObjectMap.builder().map();
|
||||
|
@ -32,6 +33,7 @@ public class FontManager extends SteppedReloadableAsset<Map<AssetLocation, Objec
|
|||
|
||||
public FontManager() {
|
||||
registerParser("stb-ttf", STBTrueTypeProvider::create);
|
||||
registerParser("free-ttf", FreeTypeProvider::load);
|
||||
}
|
||||
|
||||
public void registerParser(String id, BiFunction<JsonObject, IAssetProvider, IFontProvider> parser) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
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(); }
|
||||
}
|
|
@ -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); }
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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<FT_Face> 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<FT_Face> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -8,7 +8,6 @@ out vec4 pass_color;
|
|||
out vec2 pass_tex;
|
||||
|
||||
uniform mat4 proViewMatrix;
|
||||
uniform mat4 modelmatrix;
|
||||
|
||||
void main()
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue