mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-13 05:27:37 +00:00
OMG BEST FONT RENDERING EVER FUUUUCK
This commit is contained in:
1 parent
be4aa2592a
commit
28d7b02350
2 files changed
+139
-35
No files matched your search
@@ -1,6 +1,7 @@
|
||||
package noppes.npcs.config;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.ScaledResolution;
|
||||
import net.minecraft.client.renderer.GLAllocation;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
@@ -91,6 +92,21 @@ public class GlyphCache {
|
||||
*/
|
||||
private boolean antiAliasEnabled = false;
|
||||
|
||||
/**
|
||||
* The current Minecraft GUI scale factor (1-4). Glyphs are rasterized at fontSize * guiScaleFactor
|
||||
* so the atlas has enough texel density for the actual display resolution. This prevents
|
||||
* GL_LINEAR texture filtering from diluting alpha values at high resolutions.
|
||||
*/
|
||||
private int guiScaleFactor = 1;
|
||||
|
||||
/**
|
||||
* Return the GUI scale factor used for glyph rasterization. StringCache uses this
|
||||
* to compute an effective render scale that compensates for the larger atlas images.
|
||||
*/
|
||||
int getGuiScaleFactor() {
|
||||
return guiScaleFactor;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Temporary image for rendering a string to and then extracting the glyph images from.
|
||||
@@ -103,6 +119,7 @@ public class GlyphCache {
|
||||
private Graphics2D stringGraphics;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* All font glyphs are packed inside this image and are then loaded from here into an OpenGL texture.
|
||||
*/
|
||||
@@ -280,6 +297,7 @@ public class GlyphCache {
|
||||
|
||||
fontSize = size;
|
||||
antiAliasEnabled = antiAlias;
|
||||
guiScaleFactor = queryGuiScaleFactor();
|
||||
setRenderingHints();
|
||||
}
|
||||
|
||||
@@ -295,10 +313,50 @@ public class GlyphCache {
|
||||
|
||||
fontSize = size;
|
||||
antiAliasEnabled = antiAlias;
|
||||
guiScaleFactor = queryGuiScaleFactor();
|
||||
setRenderingHints();
|
||||
}
|
||||
}
|
||||
|
||||
private static int queryGuiScaleFactor() {
|
||||
try {
|
||||
Minecraft mc = Minecraft.getMinecraft();
|
||||
if (mc != null && mc.displayWidth > 0 && mc.displayHeight > 0) {
|
||||
ScaledResolution sr = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
|
||||
return Math.max(1, sr.getScaleFactor());
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the GUI scale factor has changed (e.g. due to window resize) and invalidate the glyph
|
||||
* cache if so. Glyphs must be re-rasterized at the new density to prevent GL_LINEAR filtering
|
||||
* from diluting alpha at high display resolutions.
|
||||
*
|
||||
* @return true if the scale factor changed and glyphs were invalidated
|
||||
*/
|
||||
boolean checkAndUpdateScaleFactor() {
|
||||
int newScale = queryGuiScaleFactor();
|
||||
if (newScale != guiScaleFactor) {
|
||||
guiScaleFactor = newScale;
|
||||
invalidateGlyphCache();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void invalidateGlyphCache() {
|
||||
glyphCache.clear();
|
||||
fontCache.clear();
|
||||
cachePosX = GLYPH_BORDER;
|
||||
cachePosY = GLYPH_BORDER;
|
||||
cacheLineHeight = 0;
|
||||
allocateGlyphCacheTexture();
|
||||
allocateStringImage(STRING_WIDTH, STRING_HEIGHT);
|
||||
}
|
||||
|
||||
int fontHeight(String s) {
|
||||
Font font = lookupFont(s.toCharArray(), 0, s.length(), 0);
|
||||
return (int) (font.getLineMetrics(s, this.fontRenderContext).getHeight());
|
||||
@@ -338,6 +396,9 @@ public class GlyphCache {
|
||||
* @return an OpenType font capable of displaying at least the first character at the start position in text
|
||||
*/
|
||||
Font lookupFont(char text[], int start, int limit, int style) {
|
||||
/* Rasterize at scaled size so atlas texel density matches display resolution */
|
||||
int renderSize = fontSize * guiScaleFactor;
|
||||
|
||||
/* Try using an already known base font; the first font in usedFonts list is the one set with setDefaultFont() */
|
||||
Iterator<Font> iterator = usedFonts.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
@@ -345,7 +406,7 @@ public class GlyphCache {
|
||||
Font font = iterator.next();
|
||||
if (font.canDisplayUpTo(text, start, limit) != start) {
|
||||
/* Return a font instance of the proper point size and style; usedFonts has only 1pt sized plain style fonts */
|
||||
return font.deriveFont(style, fontSize);
|
||||
return font.deriveFont(style, renderSize);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,7 +421,7 @@ public class GlyphCache {
|
||||
usedFonts.add(font);
|
||||
|
||||
/* Return a font instance of the proper point size and style; allFonts has only 1pt sized plain style fonts */
|
||||
return font.deriveFont(style, fontSize);
|
||||
return font.deriveFont(style, renderSize);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,7 +429,7 @@ public class GlyphCache {
|
||||
Font font = usedFonts.get(0);
|
||||
|
||||
/* Return a font instance of the proper point size and style; usedFonts only 1pt sized plain style fonts */
|
||||
return font.deriveFont(style, fontSize);
|
||||
return font.deriveFont(style, renderSize);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -460,7 +521,7 @@ public class GlyphCache {
|
||||
/* Expand bounds to account for hinting overflow */
|
||||
vectorBounds.grow(GLYPH_PADDING, GLYPH_PADDING);
|
||||
|
||||
/* Enlage the stringImage if it is too small to store the entire rendered string */
|
||||
/* Enlarge the stringImage if it is too small to store the entire rendered string */
|
||||
if (stringImage == null || vectorBounds.width > stringImage.getWidth() || vectorBounds.height > stringImage.getHeight()) {
|
||||
int width = Math.max(vectorBounds.width, stringImage.getWidth());
|
||||
int height = Math.max(vectorBounds.height, stringImage.getHeight());
|
||||
@@ -615,16 +676,19 @@ public class GlyphCache {
|
||||
stringGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
|
||||
antiAliasEnabled ? RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF);
|
||||
|
||||
if (antiAliasEnabled) {
|
||||
stringGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
|
||||
RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
|
||||
stringGraphics.setRenderingHint(RenderingHints.KEY_RENDERING,
|
||||
RenderingHints.VALUE_RENDER_QUALITY);
|
||||
} else {
|
||||
stringGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
|
||||
RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
|
||||
}
|
||||
// if (antiAliasEnabled) {
|
||||
// stringGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
|
||||
// RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
||||
// stringGraphics.setRenderingHint(RenderingHints.KEY_RENDERING,
|
||||
// RenderingHints.VALUE_RENDER_QUALITY);
|
||||
// } else {
|
||||
// stringGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
|
||||
// RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
|
||||
// }
|
||||
|
||||
stringGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
|
||||
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
||||
stringGraphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||
stringGraphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
|
||||
|
||||
fontRenderContext = stringGraphics.getFontRenderContext();
|
||||
|
||||
@@ -73,8 +73,9 @@ public class StringCache {
|
||||
*/
|
||||
private static final int STRIKETHROUGH_THICKNESS = 2;
|
||||
|
||||
// BetterFonts renders glyphs at 2x internal resolution; this scale converts to GUI coords
|
||||
private static final float RENDER_SCALE = 0.5F;
|
||||
// BetterFonts base scale: maps internal 2x glyph coords to GUI coords.
|
||||
// With scale-aware rasterization, the effective scale becomes BASE_RENDER_SCALE / guiScaleFactor.
|
||||
private static final float BASE_RENDER_SCALE = 0.5F;
|
||||
private static final boolean FONT_DEBUG_LOG = Boolean.getBoolean("cnpc.font.debug");
|
||||
private static final boolean FONT_DEBUG_DISABLE_INK_OFFSET = Boolean.getBoolean("cnpc.font.debug.disableInkOffset");
|
||||
|
||||
@@ -90,8 +91,14 @@ public class StringCache {
|
||||
return c == '"' ? "\\\"" : String.valueOf(c);
|
||||
}
|
||||
|
||||
private static float pixelSnap(float coord) {
|
||||
return (float) Math.floor(coord * 2.0F + 0.5F) * RENDER_SCALE;
|
||||
private float renderScale() {
|
||||
return BASE_RENDER_SCALE / glyphCache.getGuiScaleFactor();
|
||||
}
|
||||
|
||||
private float pixelSnap(float coord) {
|
||||
float scale = renderScale();
|
||||
float invScale = 1.0F / scale;
|
||||
return (float) Math.floor(coord * invScale + 0.5F) * scale;
|
||||
}
|
||||
|
||||
private int baselineOffset = BASELINE_OFFSET;
|
||||
@@ -468,7 +475,8 @@ public class StringCache {
|
||||
if (g.y > maxY)
|
||||
maxY = g.y;
|
||||
}
|
||||
fontHeight = (int) ((maxY + height) / 2.0F - minY / 2.0F);
|
||||
int scaleFactor = glyphCache.getGuiScaleFactor();
|
||||
fontHeight = (int) (((maxY + height) - minY) / 2.0F / scaleFactor);
|
||||
updateFontMetrics();
|
||||
}
|
||||
|
||||
@@ -477,11 +485,36 @@ public class StringCache {
|
||||
float ascent = lm.getAscent();
|
||||
float descent = lm.getDescent();
|
||||
|
||||
baselineOffset = (int) (ascent * RENDER_SCALE + 0.5f);
|
||||
/*
|
||||
* baselineOffset is added directly to startY (GUI pixels) at render time, so it must be a
|
||||
* scale-invariant GUI-pixel value. The constant BASELINE_OFFSET (7) is correct for the default
|
||||
* 18pt font and remains stable across all GUI scale factors because it operates in GUI coordinate
|
||||
* space — independent of the glyph rasterization scale.
|
||||
*
|
||||
* A dynamic formula like (ascent - padding) * renderScale is fragile: ascent comes from the
|
||||
* scaled font (fontSize * scaleFactor) and rounding/font-metric variations across scales can
|
||||
* produce values 1-2 pixels too large, visibly shifting text down within its highlight box.
|
||||
*/
|
||||
baselineOffset = BASELINE_OFFSET;
|
||||
|
||||
/*
|
||||
* underlineOffset and strikethroughOffset are multiplied by renderScale() at draw time
|
||||
* (same as glyph.x/glyph.y), so they must be in glyph-space pixels. These scale correctly
|
||||
* because ascent/descent are proportional to the rasterization size.
|
||||
*/
|
||||
underlineOffset = (int) (descent * 0.25f + 0.5f);
|
||||
strikethroughOffset = -(int) (ascent * 0.35f + 0.5f);
|
||||
}
|
||||
|
||||
private void checkForScaleChange() {
|
||||
if (glyphCache.checkAndUpdateScaleFactor()) {
|
||||
weakRefCache.clear();
|
||||
stringCache.clear();
|
||||
cacheDightGlyphs();
|
||||
updateHeight();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-cache the ASCII digits to allow for fast glyph substitution. Called once from the constructor and called any time the font selection
|
||||
* changes at runtime via setDefaultFont().
|
||||
@@ -518,6 +551,8 @@ public class StringCache {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Detect window resize: if GUI scale factor changed, invalidate all caches and re-rasterize */
|
||||
checkForScaleChange();
|
||||
|
||||
/* Make sure the entire string is cached before rendering and return its glyph representation */
|
||||
Entry entry = cacheString(str);
|
||||
@@ -615,10 +650,11 @@ public class StringCache {
|
||||
if (FONT_DEBUG_DISABLE_INK_OFFSET) {
|
||||
glyphX = glyph.x;
|
||||
}
|
||||
float x1 = pixelSnap(startX + (glyphX) * RENDER_SCALE);
|
||||
float x2 = pixelSnap(startX + (glyphX + texture.width) * RENDER_SCALE);
|
||||
float y1 = pixelSnap(startY + (glyph.y) * RENDER_SCALE);
|
||||
float y2 = pixelSnap(startY + (glyph.y + texture.height) * RENDER_SCALE);
|
||||
float rs = renderScale();
|
||||
float x1 = pixelSnap(startX + (glyphX) * rs);
|
||||
float x2 = pixelSnap(startX + (glyphX + texture.width) * rs);
|
||||
float y1 = pixelSnap(startY + (glyph.y) * rs);
|
||||
float y2 = pixelSnap(startY + (glyph.y + texture.height) * rs);
|
||||
|
||||
if (FONT_DEBUG_LOG && isSpacingDebugGlyph(c)) {
|
||||
LogWriter.info("[StringCache glyph-debug] char='" + debugGlyphLabel(c) + "' idx=" + glyph.stringIndex +
|
||||
@@ -667,10 +703,11 @@ public class StringCache {
|
||||
/* Draw underline under glyph if the style is enabled */
|
||||
if ((renderStyle & ColorCode.UNDERLINE) != 0) {
|
||||
/* The divide by 2.0F is needed to align with the scaled GUI coordinate system; startX/startY are already scaled */
|
||||
float x1 = pixelSnap(startX + (glyph.x - glyphSpace) * RENDER_SCALE);
|
||||
float x2 = pixelSnap(startX + (glyph.x + glyph.advance) * RENDER_SCALE);
|
||||
float y1 = pixelSnap(startY + (underlineOffset) * RENDER_SCALE);
|
||||
float y2 = pixelSnap(startY + (underlineOffset + UNDERLINE_THICKNESS) * RENDER_SCALE);
|
||||
float urs = renderScale();
|
||||
float x1 = pixelSnap(startX + (glyph.x - glyphSpace) * urs);
|
||||
float x2 = pixelSnap(startX + (glyph.x + glyph.advance) * urs);
|
||||
float y1 = pixelSnap(startY + (underlineOffset) * urs);
|
||||
float y2 = pixelSnap(startY + (underlineOffset + UNDERLINE_THICKNESS) * urs);
|
||||
|
||||
tessellator.addVertex(x1, y1, 0);
|
||||
tessellator.addVertex(x1, y2, 0);
|
||||
@@ -681,10 +718,11 @@ public class StringCache {
|
||||
/* Draw strikethrough in the middle of glyph if the style is enabled */
|
||||
if ((renderStyle & ColorCode.STRIKETHROUGH) != 0) {
|
||||
/* The divide by 2.0F is needed to align with the scaled GUI coordinate system; startX/startY are already scaled */
|
||||
float x1 = pixelSnap(startX + (glyph.x - glyphSpace) * RENDER_SCALE);
|
||||
float x2 = pixelSnap(startX + (glyph.x + glyph.advance) * RENDER_SCALE);
|
||||
float y1 = pixelSnap(startY + (strikethroughOffset) * RENDER_SCALE);
|
||||
float y2 = pixelSnap(startY + (strikethroughOffset + STRIKETHROUGH_THICKNESS) * RENDER_SCALE);
|
||||
float srs = renderScale();
|
||||
float x1 = pixelSnap(startX + (glyph.x - glyphSpace) * srs);
|
||||
float x2 = pixelSnap(startX + (glyph.x + glyph.advance) * srs);
|
||||
float y1 = pixelSnap(startY + (strikethroughOffset) * srs);
|
||||
float y2 = pixelSnap(startY + (strikethroughOffset + STRIKETHROUGH_THICKNESS) * srs);
|
||||
|
||||
tessellator.addVertex(x1, y1, 0);
|
||||
tessellator.addVertex(x1, y2, 0);
|
||||
@@ -700,7 +738,7 @@ public class StringCache {
|
||||
|
||||
|
||||
/* Return total horizontal advance (slightly wider than the bounding box, but close enough for centering strings) */
|
||||
return entry.advance / 2;
|
||||
return (int) (entry.advance * renderScale());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -715,11 +753,13 @@ public class StringCache {
|
||||
return 0;
|
||||
}
|
||||
|
||||
checkForScaleChange();
|
||||
|
||||
/* Make sure the entire string is cached and rendered since it will probably be used again in a renderString() call */
|
||||
Entry entry = cacheString(str);
|
||||
|
||||
/* Return total horizontal advance (slightly wider than the bounding box, but close enough for centering strings) */
|
||||
return entry.advance / 2;
|
||||
return (int) (entry.advance * renderScale());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -738,8 +778,8 @@ public class StringCache {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Convert the width from GUI coordinate system to pixels */
|
||||
width += width;
|
||||
/* Convert the width from GUI coordinate system to internal glyph pixels */
|
||||
width = (int) (width / renderScale());
|
||||
|
||||
/* The glyph array for a string is sorted by the string's logical character position */
|
||||
Glyph glyphs[] = cacheString(str).glyphs;
|
||||
|
||||
Reference in new issue
Block a user