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

252 lines
8.5 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.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
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.TextureAtlas;
import speiger.src.coreengine.rendering.textures.custom.TextureAtlas.AtlasEntry;
import speiger.src.coreengine.rendering.textures.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 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().getX());
info.addProperty("charHeight", key.getKey().getY());
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();
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);
};
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);
ObjectObjectPair<ObjectObjectPair<Vec2i, BufferedImage>, List<WrittenChar>> result = ObjectObjectPair.mutableValue(new ObjectArrayList<>());
builder.buildHollow((K, V) -> {
BufferedImage image = new BufferedImage(K.getX(), K.getY(), 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 % 1000 == 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
{
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));
}
}
}