Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 93 additions & 6 deletions src/main/java/meteordevelopment/meteorclient/renderer/Fonts.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,36 @@
import meteordevelopment.meteorclient.utils.render.FontUtils;

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Optional;

import static meteordevelopment.meteorclient.MeteorClient.mc;

public class Fonts {
public static final String[] BUILTIN_FONTS = {"JetBrains Mono", "Comfortaa", "Tw Cen MT", "Pixelation"};
private static final List<String> FALLBACK_FONT_FAMILIES = List.of(
"Noto Sans CJK SC",
"Noto Sans SC",
"Microsoft YaHei",
"Microsoft YaHei UI",
"DengXian",
"SimHei",
"SimSun",
"KaiTi",
"Microsoft JhengHei",
"Malgun Gothic",
"Yu Gothic",
"MS Gothic",
"Source Han Sans SC",
"WenQuanYi Zen Hei",
"PingFang SC",
"Hiragino Sans GB"
);

public static String DEFAULT_FONT_FAMILY;
public static FontFace DEFAULT_FONT;
Expand Down Expand Up @@ -59,23 +81,26 @@ public static void refresh() {
}

public static void load(FontFace fontFace) {
if (RENDERER != null) {
if (RENDERER.fontFace.equals(fontFace)) return;
else RENDERER.destroy();
}
CustomTextRenderer previous = RENDERER;
if (previous != null && previous.fontFace.equals(fontFace)) return;

CustomTextRenderer replacement;
try {
RENDERER = new CustomTextRenderer(fontFace);
MeteorClient.EVENT_BUS.post(CustomFontChangedEvent.get());
replacement = new CustomTextRenderer(fontFace);
} catch (Exception e) {
if (fontFace.equals(DEFAULT_FONT)) {
throw new RuntimeException("Failed to load default font: " + fontFace, e);
}

MeteorClient.LOG.error("Failed to load font: {}", fontFace, e);
load(Fonts.DEFAULT_FONT);
return;
}

RENDERER = replacement;
if (previous != null) previous.destroy();
MeteorClient.EVENT_BUS.post(CustomFontChangedEvent.get());

if (mc.screen instanceof WidgetScreen widgetScreen && Config.get().customFont.get()) {
widgetScreen.invalidate();
}
Expand All @@ -90,4 +115,66 @@ public static FontFamily getFamily(String name) {

return null;
}

public static Optional<FontFace> getFallbackFont(FontFace primary) {
for (String familyName : FALLBACK_FONT_FAMILIES) {
FontFace font = findFallbackFont(familyName, primary);
if (font != null) return Optional.of(font);
}

return Optional.empty();
}

public static List<ByteBuffer> readFontBuffers(FontFace primary) throws IOException {
List<ByteBuffer> buffers = new ArrayList<>();
buffers.add(primary.readToDirectByteBuffer());

getFallbackFont(primary)
.flatMap(Fonts::readFallbackFont)
.ifPresent(buffers::add);

return List.copyOf(buffers);
}

private static Optional<ByteBuffer> readFallbackFont(FontFace font) {
try {
return Optional.of(font.readToDirectByteBuffer());
} catch (IOException e) {
MeteorClient.LOG.warn("Failed to load fallback font: {}", font, e);
return Optional.empty();
}
}

private static FontFace findFallbackFont(String familyName, FontFace primary) {
FontFamily family = getFamily(familyName);

if (family == null) {
String needle = familyName.toLowerCase(Locale.ROOT);

for (FontFamily fontFamily : FONT_FAMILIES) {
if (fontFamily.getName().toLowerCase(Locale.ROOT).contains(needle)) {
family = fontFamily;
break;
}
}
}

if (family == null) return null;
if (family.getName().equalsIgnoreCase(primary.info.family())) return null;

FontFace styleMatch = family.get(primary.info.type());
if (styleMatch != null) return styleMatch;

FontFace regular = family.get(FontInfo.Type.Regular);
if (regular != null) return regular;

for (FontInfo.Type type : FontInfo.Type.values()) {
if (type == primary.info.type() || type == FontInfo.Type.Regular) continue;

FontFace font = family.get(type);
if (font != null) return font;
}

return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

package meteordevelopment.meteorclient.renderer.text;

import meteordevelopment.meteorclient.renderer.Fonts;
import meteordevelopment.meteorclient.renderer.MeshBuilder;
import meteordevelopment.meteorclient.renderer.MeshRenderer;
import meteordevelopment.meteorclient.renderer.MeteorRenderPipelines;
Expand All @@ -13,30 +14,38 @@

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.List;

public class CustomTextRenderer implements TextRenderer {
public static final Color SHADOW_COLOR = new Color(60, 60, 60, 180);

private final MeshBuilder mesh = new MeshBuilder(MeteorRenderPipelines.UI_TEXT);
private final Color shadowColor = new Color(SHADOW_COLOR);

public final FontFace fontFace;

private final List<ByteBuffer> fontBuffers;
private final Font[] fonts;
private Font font;

private boolean building;
private boolean scaleOnly;
private boolean destroyed;
private double fontScale = 1;
private double scale = 1;

public CustomTextRenderer(FontFace fontFace) throws IOException {
this.fontFace = fontFace;

ByteBuffer buffer = fontFace.readToDirectByteBuffer();

fonts = new Font[5];
for (int i = 0; i < fonts.length; i++) {
fonts[i] = new Font(buffer, (int) Math.round(27 * ((i * 0.5) + 1)));
this.fontBuffers = Fonts.readFontBuffers(fontFace);

this.fonts = new Font[5];
try {
for (int i = 0; i < fonts.length; i++) {
fonts[i] = createFont((int) Math.round(27 * ((i * 0.5) + 1)));
}
} catch (RuntimeException | Error e) {
closeFonts();
throw e;
}
}

Expand All @@ -47,6 +56,7 @@ public void setAlpha(double a) {

@Override
public void begin(double scale, boolean scaleOnly, boolean big) {
if (destroyed) throw new IllegalStateException("CustomTextRenderer has already been destroyed.");
if (building) throw new RuntimeException("CustomTextRenderer.begin() called twice");

if (!scaleOnly) mesh.begin();
Expand Down Expand Up @@ -94,13 +104,10 @@ public double render(String text, double x, double y, Color color, boolean shado

double width;
if (shadow) {
int preShadowA = SHADOW_COLOR.a;
SHADOW_COLOR.a = (int) (color.a / 255.0 * preShadowA);
shadowColor.a = (int) (color.a / 255.0 * SHADOW_COLOR.a);

width = font.render(mesh, text, x + fontScale * scale / 1.5, y + fontScale * scale / 1.5, SHADOW_COLOR, scale / 1.5);
width = font.render(mesh, text, x + fontScale * scale / 1.5, y + fontScale * scale / 1.5, shadowColor, scale / 1.5);
font.render(mesh, text, x, y, color, scale / 1.5);

SHADOW_COLOR.a = preShadowA;
} else {
width = font.render(mesh, text, x, y, color, scale / 1.5);
}
Expand All @@ -118,24 +125,40 @@ public boolean isBuilding() {
public void end() {
if (!building) throw new RuntimeException("CustomTextRenderer.end() called without calling begin()");

if (!scaleOnly) {
mesh.end();

MeshRenderer.begin()
.attachments(Minecraft.getInstance().getMainRenderTarget())
.pipeline(MeteorRenderPipelines.UI_TEXT)
.mesh(mesh)
.sampler("u_Texture", font.texture.getTextureView(), font.texture.getSampler())
.end();
try {
if (!scaleOnly) {
mesh.end();
font.uploadPendingGlyphs();

MeshRenderer.begin()
.attachments(Minecraft.getInstance().getMainRenderTarget())
.pipeline(MeteorRenderPipelines.UI_TEXT)
.mesh(mesh)
.sampler("u_Texture", font.texture.getTextureView(), font.texture.getSampler())
.end();
}
} finally {
building = false;
scaleOnly = false;
scale = 1;
}
}

building = false;
scale = 1;
public Font createFont(int height) {
if (destroyed) throw new IllegalStateException("CustomTextRenderer has already been destroyed.");
return new Font(fontBuffers, height);
}

public void destroy() {
for (Font font : this.fonts) {
font.texture.close();
if (destroyed) return;

closeFonts();
destroyed = true;
}

private void closeFonts() {
for (Font font : fonts) {
if (font != null) font.close();
}
}
}
Loading