294 lines
11 KiB
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.texturesOld.atlas.AtlasStitcher;
|
|
import speiger.src.coreengine.rendering.texturesOld.atlas.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;
|
|
}
|
|
|
|
|
|
}
|
|
}
|