SimpleJavaEngine/src/main/java/speiger/src/coreengine/rendering/gui/helper/FontBuilder.java

294 lines
11 KiB
Java

package speiger.src.coreengine.rendering.gui.helper;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.stb.STBTTFontinfo;
import org.lwjgl.stb.STBTruetype;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import speiger.src.collections.ints.collections.IntIterator;
import speiger.src.collections.ints.utils.IntIterators;
import speiger.src.collections.objects.lists.ObjectArrayList;
import speiger.src.collections.objects.maps.impl.hash.Object2ObjectLinkedOpenHashMap;
import speiger.src.collections.objects.misc.pairs.ObjectObjectPair;
import speiger.src.coreengine.assets.AssetLocation;
import speiger.src.coreengine.math.vector.ints.Vec2i;
import speiger.src.coreengine.rendering.gui.renderer.IFontRenderer.CharInstance;
import speiger.src.coreengine.rendering.textures.custom.AtlasStitcher;
import speiger.src.coreengine.rendering.textures.custom.AtlasStitcher.Entry;
import speiger.src.coreengine.rendering.texturesOld.custom.TextureAtlas;
import speiger.src.coreengine.rendering.texturesOld.custom.TextureAtlas.AtlasEntry;
import speiger.src.coreengine.rendering.texturesOld.custom.TextureAtlas.Builder;
public class FontBuilder {
public static final int LITERAL = 1;
public static final int PLAIN = 2;
public static final int BOLD = 4;
public static void main(String...args) {
generateFont();
}
public static void generateFont() {
float size = 48F;
Path path = Paths.get("resources/font/Roboto-Medium.ttf");
try(STBTTFontinfo info = STBTTFontinfo.malloc()) {
byte[] data = Files.readAllBytes(path);
ByteBuffer buffer = MemoryUtil.memAlloc(data.length).put(data).flip();
STBTruetype.stbtt_InitFont(info, buffer);
float point = STBTruetype.stbtt_ScaleForPixelHeight(info, size);
float ascent = 0F;
int index = 0;
try(MemoryStack stack = MemoryStack.stackPush()) {
IntBuffer ascentB = stack.mallocInt(1);
IntBuffer descentB = stack.mallocInt(1);
IntBuffer lineGapB = stack.mallocInt(1);
STBTruetype.stbtt_GetFontVMetrics(info, ascentB, descentB, lineGapB);
ascent = ascentB.get(0) * point;
index = STBTruetype.nstbtt_FindGlyphIndex(info.address(), GLFW.GLFW_KEY_C);
}
System.out.println("Testing: Scale="+point+", Ascent="+ascent+", index="+index);
}
catch(Exception e) {
e.printStackTrace();
}
}
public static ObjectObjectPair<BufferedImage, JsonObject> createBitmapFont(InputStream stream, float size) {
return createBitmapFont(stream, size, "UTF-8");
}
public static ObjectObjectPair<BufferedImage, JsonObject> createBitmapFont(InputStream stream, float size, String charset) {
ObjectObjectPair<ObjectObjectPair<Vec2i, BufferedImage>, List<WrittenChar>> result = createBitmapFont(stream, charset, PLAIN | BOLD, size);
if(result == null) return null;
JsonArray array = new JsonArray();
result.getValue().forEach(T -> array.add(T.seralize()));
JsonObject info = new JsonObject();
ObjectObjectPair<Vec2i, BufferedImage> key = result.getKey();
info.addProperty("width", key.getValue().getWidth());
info.addProperty("height", key.getValue().getHeight());
info.addProperty("base", key.getKey().x());
info.addProperty("charHeight", key.getKey().y());
info.addProperty("tabs", 4);
JsonObject data = new JsonObject();
data.addProperty("type", "bitmap");
data.addProperty("file", "?");
data.add("info", info);
data.add("chars", array);
return ObjectObjectPair.of(key.getValue(), data);
}
public static ObjectObjectPair<ObjectObjectPair<Vec2i, BufferedImage>, List<WrittenChar>> createBitmapFont(InputStream ttf, String characters, int flags, float size) {
try {
Map<AssetLocation, CharData> toDraw = new Object2ObjectLinkedOpenHashMap<>();
Builder builder = TextureAtlas.create();
AtlasStitcher<CharData> newBuilder = new AtlasStitcher<>(Integer.MAX_VALUE, Integer.MAX_VALUE);
Consumer<CharData> data = T -> {
AssetLocation location = T.asLocation();
toDraw.put(location, T);
if(T.width > 0 && !builder.add(location, T.width, T.getExtraY(T.height), 2)) throw new IllegalStateException("Character: "+location+" isnt Accepted, W="+T.width+", H="+T.height);
newBuilder.add(T);
};
Font font = Font.createFont(Font.TRUETYPE_FONT, ttf).deriveFont(size);
String validChars = (flags & LITERAL) != 0 ? characters : getChars(characters, font);
if((flags & PLAIN) != 0) loadFontData(font, validChars, data);
if((flags & BOLD) != 0) loadFontData(font.deriveFont(Font.BOLD), validChars, data);
newBuilder.stitch();
if(newBuilder.isValid()) {
BufferedImage image = new BufferedImage(newBuilder.width(), newBuilder.height(), BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = image.createGraphics();
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphics.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
graphics.setFont(font);
FontMetrics metric = graphics.getFontMetrics();
ObjectObjectPair<ObjectObjectPair<Vec2i, BufferedImage>, List<WrittenChar>> result = ObjectObjectPair.mutableValue(new ObjectArrayList<>());
int[] extra = new int[1];
newBuilder.process((D, X, Y) -> {
extra[0] = D.extraY;
graphics.setFont(D.getFont());
graphics.setColor(Color.WHITE);
graphics.drawString(new String(Character.toChars(D.getLetter())), X - D.xOffset, D.getExtraY(Y + metric.getAscent()));
result.getValue().add(new WrittenChar(D.getLetter(), X, Y, D.width(), D.height(), D.isBold()));
});
result.setKey(ObjectObjectPair.of(Vec2i.of(metric.getAscent() + extra[0], metric.getHeight() + extra[0]), image));
graphics.dispose();
return result;
}
ObjectObjectPair<ObjectObjectPair<Vec2i, BufferedImage>, List<WrittenChar>> result = ObjectObjectPair.mutableValue(new ObjectArrayList<>());
builder.buildHollow((K, V) -> {
BufferedImage image = new BufferedImage(K.x(), K.y(), BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = image.createGraphics();
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphics.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
graphics.setFont(font);
FontMetrics metric = graphics.getFontMetrics();
int extra = 0;
for(AtlasEntry entry : V) {
CharData pair = toDraw.remove(entry.getLocation());
extra = pair.extraY;
graphics.setFont(pair.getFont());
graphics.setColor(Color.WHITE);
graphics.drawString(new String(Character.toChars(pair.getLetter())), entry.getX() - pair.xOffset, pair.getExtraY(entry.getY() + metric.getAscent()));
result.getValue().add(new WrittenChar(pair.getLetter(), entry.getX(), entry.getY(), entry.getWidth(), entry.getHeight(), pair.isBold()));
}
toDraw.values().forEach(T -> result.getValue().add(new WrittenChar(T.getLetter(), 0, 0, 0, 0, T.isBold())));
result.setKey(ObjectObjectPair.of(Vec2i.of(metric.getAscent() + extra, metric.getHeight() + extra), image));
graphics.dispose();
});
return result;
}
catch(Exception e) {
e.printStackTrace();
return null;
}
}
private static float convert(float pos, float width) {
return pos / width;
}
private static String getChars(String s, Font font) {
CharsetEncoder encoder = Charset.forName(s).newEncoder();
StringBuilder builder = new StringBuilder();
for(int c = 0;c < 0x110000;c++) {
if(!Character.isDefined(c)) continue;
char[] chars = Character.toChars(c);
if(encoder.canEncode(new String(chars)) && font.canDisplay(c)) builder.append(chars);
}
return builder.toString();
}
private static void loadFontData(Font font, String chars, Consumer<CharData> listener) {
BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = (Graphics2D)image.getGraphics();
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
graphics.setFont(font);
FontMetrics metric = graphics.getFontMetrics();
int extraHeight = 0;
List<CharData> data = new ObjectArrayList<>();
int c = 0;
for(IntIterator iter = IntIterators.wrap(chars.codePoints().iterator());iter.hasNext();c++) {
int next = iter.nextInt();
Rectangle rect = font.layoutGlyphVector(graphics.getFontRenderContext(), Character.toChars(next), 0, Character.charCount(next), 0).getGlyphPixelBounds(0, graphics.getFontRenderContext(), 0.0F, 0.0F);
extraHeight = Math.min(extraHeight, rect.y);
data.add(new CharData(font, next, rect, metric.charWidth(next), metric.getHeight(), font.isBold()));
if(c % 100 == 0) System.out.println("Rendered: "+c+" / "+chars.length()+" Chars");
}
extraHeight = -(extraHeight + metric.getAscent());
for(int i = 0,m = data.size();i < m;listener.accept(data.get(i++).offset(extraHeight)));
graphics.dispose();
}
public static class WrittenChar {
int letter;
int x;
int y;
int width;
int height;
boolean bold;
public WrittenChar(int letter, int x, int y, int width, int height, boolean bold) {
this.letter = letter;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.bold = bold;
}
public JsonObject seralize() {
JsonObject obj = new JsonObject();
obj.addProperty("char", letter);
obj.addProperty("minX", x);
obj.addProperty("minY", y);
obj.addProperty("maxX", x + width);
obj.addProperty("maxY", y + height);
obj.addProperty("bold", bold);
return obj;
}
public CharInstance create(int textureWidth, int textureHeight) {
return new CharInstance(letter, width, height, convert(x, textureWidth), convert(y, textureHeight), convert(x + width, textureWidth), convert(y + height, textureHeight), width, bold);
}
}
private static class CharData implements Entry {
Font font;
int letter;
int xOffset;
int width;
int height;
boolean bold;
int extraY = 0;
public CharData(Font font, int letter, Rectangle bounds, int width, int height, boolean bold) {
this.font = font;
this.letter = letter;
if(bounds.x >= 0) this.width = width;
else {
this.width = bounds.width == 0 ? width : bounds.width;
xOffset = bounds.x;
}
this.height = height;
this.bold = bold;
}
private CharData offset(int offset) {
extraY += offset;
return this;
}
public boolean isBold() { return bold; }
public Font getFont() { return font; }
public int getExtraY(int asent) { return extraY + asent; }
public int getLetter() { return letter; }
public AssetLocation asLocation() { return AssetLocation.of("base", font.getFontName().replaceAll(" ", "_")+(bold ? "_Bold" : "")+"_"+letter); }
@Override
public AssetLocation id() {
return asLocation();
}
@Override
public int width() {
return this.width + 2;
}
@Override
public int height() {
return this.height + 2;
}
}
}