render font glyphs at device resolution to fix pixelation
glyphs were rasterized at gui-space size and then magnified by the gui scale with nearest-neighbour sampling, making every custom font blocky. rasters are now built at font-size * gui-scale (one texel per screen pixel) and drawn through a 1/scale pose, so text is 1:1 and antialiased at any gui scale. measurements convert back to gui units and a gui scale change re-rasterizes everything
This commit is contained in:
@@ -82,6 +82,7 @@ public final class FrosFont {
|
||||
private static FontMetrics metrics;
|
||||
private static String loadedName;
|
||||
private static float loadedSize = Float.NaN;
|
||||
private static int loadedScale = 1;
|
||||
private static long lastAttempt;
|
||||
private static int glyphCounter;
|
||||
|
||||
@@ -92,7 +93,7 @@ public final class FrosFont {
|
||||
if (text == null || text.isEmpty()) return 0;
|
||||
sync();
|
||||
if (awtFont == null) return mc.font.width(text);
|
||||
return metrics.stringWidth(stripCodes(text));
|
||||
return toGui(metrics.stringWidth(stripCodes(text)));
|
||||
}
|
||||
|
||||
public static int width(Component component) {
|
||||
@@ -104,7 +105,20 @@ public final class FrosFont {
|
||||
|
||||
public static int lineHeight() {
|
||||
sync();
|
||||
return awtFont == null ? mc.font.lineHeight : metrics.getHeight();
|
||||
return awtFont == null ? mc.font.lineHeight : toGui(metrics.getHeight());
|
||||
}
|
||||
|
||||
/** converts a raster pixel measurement to gui units */
|
||||
private static int toGui(int pixels) {
|
||||
return (pixels + loadedScale - 1) / loadedScale;
|
||||
}
|
||||
|
||||
private static int currentScale() {
|
||||
try {
|
||||
return Math.max(1, (int) Math.round(mc.getWindow().getGuiScale()));
|
||||
} catch (Exception e) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
public static void draw(GuiGraphics context, String text, int x, int y, int color) {
|
||||
@@ -140,20 +154,22 @@ public final class FrosFont {
|
||||
String name = gui == null || vanilla ? "" : gui.font.getValue();
|
||||
int size = gui == null ? 10 : gui.fontSize.getValue();
|
||||
if (name == null) name = "";
|
||||
int scale = currentScale();
|
||||
|
||||
boolean changed = !Objects.equals(name, loadedName) || size != loadedSize;
|
||||
boolean changed = !Objects.equals(name, loadedName) || size != loadedSize || scale != loadedScale;
|
||||
if (!changed) {
|
||||
boolean missing = awtFont == null && !name.isBlank();
|
||||
// retry a missing file every couple of seconds so dropping one
|
||||
// into the folder loads without touching the setting again
|
||||
if (!missing || System.currentTimeMillis() - lastAttempt < 2000) return;
|
||||
}
|
||||
reset(name, size, changed);
|
||||
reset(name, size, scale, changed);
|
||||
}
|
||||
|
||||
private static void reset(String name, float size, boolean verbose) {
|
||||
private static void reset(String name, float size, int scale, boolean verbose) {
|
||||
loadedName = name;
|
||||
loadedSize = size;
|
||||
loadedScale = scale;
|
||||
lastAttempt = System.currentTimeMillis();
|
||||
awtFont = null;
|
||||
metrics = null;
|
||||
@@ -170,13 +186,13 @@ public final class FrosFont {
|
||||
Path file = resolveFontFile(name);
|
||||
if (file != null) {
|
||||
applyFont(java.awt.Font.createFont(java.awt.Font.TRUETYPE_FONT, file.toFile()),
|
||||
size, "Loaded font " + file.getFileName(), verbose);
|
||||
size * scale, "Loaded font " + file.getFileName(), verbose);
|
||||
return;
|
||||
}
|
||||
|
||||
java.awt.Font installed = installedFont(name);
|
||||
if (installed != null) {
|
||||
applyFont(installed, size, "Using system font '" + name + "'", verbose);
|
||||
applyFont(installed, size * scale, "Using system font '" + name + "'", verbose);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -279,18 +295,28 @@ public final class FrosFont {
|
||||
if (glyph == null) return;
|
||||
GLYPHS.put(key, glyph);
|
||||
}
|
||||
|
||||
// the bitmap is rasterized at device resolution (one texel per screen
|
||||
// pixel); scale the pose down into gui space so the gpu never has to
|
||||
// magnify it with nearest-neighbour sampling
|
||||
float scale = loadedScale;
|
||||
context.pose().pushMatrix();
|
||||
context.pose().scale(1f / scale, 1f / scale);
|
||||
context.pose().translate((x - 1) * scale, (y - 1) * scale);
|
||||
context.blit(RenderPipelines.GUI_TEXTURED, glyph.id(),
|
||||
x - 1, y - 1, 0.0f, 0.0f, glyph.width(), glyph.height(), glyph.width(), glyph.height());
|
||||
0, 0, 0.0f, 0.0f, glyph.width(), glyph.height(), glyph.width(), glyph.height());
|
||||
context.pose().popMatrix();
|
||||
}
|
||||
|
||||
private static Glyph createGlyph(String text, int baseColor) {
|
||||
String plain = stripCodes(text);
|
||||
if (plain.isEmpty()) return null;
|
||||
|
||||
int pad = loadedScale;
|
||||
int width = Math.max(1, metrics.stringWidth(plain));
|
||||
int height = metrics.getHeight();
|
||||
int imageWidth = width + 2;
|
||||
int imageHeight = height + 2;
|
||||
int imageWidth = width + 2 * pad;
|
||||
int imageHeight = height + 2 * pad;
|
||||
|
||||
BufferedImage image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D graphics = image.createGraphics();
|
||||
@@ -319,7 +345,7 @@ public final class FrosFont {
|
||||
}
|
||||
|
||||
private static void drawSegments(Graphics2D graphics, String text, int baseColor) {
|
||||
int baseline = metrics.getAscent() + 1;
|
||||
int baseline = metrics.getAscent() + loadedScale;
|
||||
int cursor = 1;
|
||||
int current = baseColor;
|
||||
int segmentStart = 0;
|
||||
|
||||
Reference in New Issue
Block a user