Finishing up the fonts \o/

This commit is contained in:
Speiger 2024-08-09 18:23:39 +02:00
parent 5fd1ff4904
commit 96d3c5b345
17 changed files with 381 additions and 87 deletions

View File

@ -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'
}
}

View File

@ -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");

View File

@ -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();

View File

@ -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;

View File

@ -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) {

View File

@ -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;

View File

@ -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);
}
}
}

View File

@ -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(); }
}

View File

@ -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); }
}

View File

@ -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) {

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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();
}

View File

@ -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() {

View File

@ -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);

View File

@ -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 }

View File

@ -8,7 +8,6 @@ out vec4 pass_color;
out vec2 pass_tex;
uniform mat4 proViewMatrix;
uniform mat4 modelmatrix;
void main()
{