252 lines
8.5 KiB
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));
|
|
}
|
|
}
|
|
}
|