Code/net/minecraft/client/gui/font/providers/UnihexProvider.java

506 lines
19 KiB
Java
Raw Normal View History

2025-07-01 06:20:03 +00:00
package net.minecraft.client.gui.font.providers;
import com.google.common.annotations.VisibleForTesting;
import com.mojang.blaze3d.font.GlyphInfo;
import com.mojang.blaze3d.font.GlyphProvider;
import com.mojang.blaze3d.font.SheetGlyphInfo;
import com.mojang.blaze3d.platform.NativeImage;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.textures.GpuTexture;
import com.mojang.datafixers.util.Either;
import com.mojang.logging.LogUtils;
import com.mojang.serialization.Codec;
import com.mojang.serialization.DataResult;
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import com.mojang.serialization.codecs.RecordCodecBuilder.Instance;
import it.unimi.dsi.fastutil.bytes.ByteArrayList;
import it.unimi.dsi.fastutil.bytes.ByteList;
import it.unimi.dsi.fastutil.ints.IntSet;
import java.io.IOException;
import java.io.InputStream;
import java.nio.IntBuffer;
import java.util.List;
import java.util.function.Function;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import javax.annotation.Nullable;
import net.minecraft.client.gui.font.CodepointMap;
import net.minecraft.client.gui.font.glyphs.BakedGlyph;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.util.ExtraCodecs;
import net.minecraft.util.FastBufferedInputStream;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import org.lwjgl.system.MemoryUtil;
import org.slf4j.Logger;
@OnlyIn(Dist.CLIENT)
public class UnihexProvider implements GlyphProvider {
static final Logger LOGGER = LogUtils.getLogger();
private static final int GLYPH_HEIGHT = 16;
private static final int DIGITS_PER_BYTE = 2;
private static final int DIGITS_FOR_WIDTH_8 = 32;
private static final int DIGITS_FOR_WIDTH_16 = 64;
private static final int DIGITS_FOR_WIDTH_24 = 96;
private static final int DIGITS_FOR_WIDTH_32 = 128;
private final CodepointMap<UnihexProvider.Glyph> glyphs;
UnihexProvider(CodepointMap<UnihexProvider.Glyph> p_285457_) {
this.glyphs = p_285457_;
}
@Nullable
@Override
public GlyphInfo getGlyph(int p_285239_) {
return this.glyphs.get(p_285239_);
}
@Override
public IntSet getSupportedGlyphs() {
return this.glyphs.keySet();
}
@VisibleForTesting
static void unpackBitsToBytes(IntBuffer p_285211_, int p_285508_, int p_285312_, int p_285412_) {
int i = 32 - p_285312_ - 1;
int j = 32 - p_285412_ - 1;
for (int k = i; k >= j; k--) {
if (k < 32 && k >= 0) {
boolean flag = (p_285508_ >> k & 1) != 0;
p_285211_.put(flag ? -1 : 0);
} else {
p_285211_.put(0);
}
}
}
static void unpackBitsToBytes(IntBuffer p_285283_, UnihexProvider.LineData p_285485_, int p_284940_, int p_284950_) {
for (int i = 0; i < 16; i++) {
int j = p_285485_.line(i);
unpackBitsToBytes(p_285283_, j, p_284940_, p_284950_);
}
}
@VisibleForTesting
static void readFromStream(InputStream p_285315_, UnihexProvider.ReaderOutput p_285353_) throws IOException {
int i = 0;
ByteList bytelist = new ByteArrayList(128);
while (true) {
boolean flag = copyUntil(p_285315_, bytelist, 58);
int j = bytelist.size();
if (j == 0 && !flag) {
return;
}
if (!flag || j != 4 && j != 5 && j != 6) {
throw new IllegalArgumentException("Invalid entry at line " + i + ": expected 4, 5 or 6 hex digits followed by a colon");
}
int k = 0;
for (int l = 0; l < j; l++) {
k = k << 4 | decodeHex(i, bytelist.getByte(l));
}
bytelist.clear();
copyUntil(p_285315_, bytelist, 10);
int i1 = bytelist.size();
UnihexProvider.LineData unihexprovider$linedata = switch (i1) {
case 32 -> UnihexProvider.ByteContents.read(i, bytelist);
case 64 -> UnihexProvider.ShortContents.read(i, bytelist);
case 96 -> UnihexProvider.IntContents.read24(i, bytelist);
case 128 -> UnihexProvider.IntContents.read32(i, bytelist);
default -> throw new IllegalArgumentException(
"Invalid entry at line " + i + ": expected hex number describing (8,16,24,32) x 16 bitmap, followed by a new line"
);
};
p_285353_.accept(k, unihexprovider$linedata);
i++;
bytelist.clear();
}
}
static int decodeHex(int p_285205_, ByteList p_285268_, int p_285345_) {
return decodeHex(p_285205_, p_285268_.getByte(p_285345_));
}
private static int decodeHex(int p_284952_, byte p_285036_) {
return switch (p_285036_) {
case 48 -> 0;
case 49 -> 1;
case 50 -> 2;
case 51 -> 3;
case 52 -> 4;
case 53 -> 5;
case 54 -> 6;
case 55 -> 7;
case 56 -> 8;
case 57 -> 9;
default -> throw new IllegalArgumentException("Invalid entry at line " + p_284952_ + ": expected hex digit, got " + (char)p_285036_);
case 65 -> 10;
case 66 -> 11;
case 67 -> 12;
case 68 -> 13;
case 69 -> 14;
case 70 -> 15;
};
}
private static boolean copyUntil(InputStream p_284994_, ByteList p_285351_, int p_285177_) throws IOException {
while (true) {
int i = p_284994_.read();
if (i == -1) {
return false;
}
if (i == p_285177_) {
return true;
}
p_285351_.add((byte)i);
}
}
@OnlyIn(Dist.CLIENT)
record ByteContents(byte[] contents) implements UnihexProvider.LineData {
@Override
public int line(int p_285203_) {
return this.contents[p_285203_] << 24;
}
static UnihexProvider.LineData read(int p_285080_, ByteList p_285481_) {
byte[] abyte = new byte[16];
int i = 0;
for (int j = 0; j < 16; j++) {
int k = UnihexProvider.decodeHex(p_285080_, p_285481_, i++);
int l = UnihexProvider.decodeHex(p_285080_, p_285481_, i++);
byte b0 = (byte)(k << 4 | l);
abyte[j] = b0;
}
return new UnihexProvider.ByteContents(abyte);
}
@Override
public int bitWidth() {
return 8;
}
}
@OnlyIn(Dist.CLIENT)
public static class Definition implements GlyphProviderDefinition {
public static final MapCodec<UnihexProvider.Definition> CODEC = RecordCodecBuilder.mapCodec(
p_286579_ -> p_286579_.group(
ResourceLocation.CODEC.fieldOf("hex_file").forGetter(p_286591_ -> p_286591_.hexFile),
UnihexProvider.OverrideRange.CODEC.listOf().fieldOf("size_overrides").forGetter(p_286528_ -> p_286528_.sizeOverrides)
)
.apply(p_286579_, UnihexProvider.Definition::new)
);
private final ResourceLocation hexFile;
private final List<UnihexProvider.OverrideRange> sizeOverrides;
private Definition(ResourceLocation p_286378_, List<UnihexProvider.OverrideRange> p_286770_) {
this.hexFile = p_286378_;
this.sizeOverrides = p_286770_;
}
@Override
public GlyphProviderType type() {
return GlyphProviderType.UNIHEX;
}
@Override
public Either<GlyphProviderDefinition.Loader, GlyphProviderDefinition.Reference> unpack() {
return Either.left(this::load);
}
private GlyphProvider load(ResourceManager p_286472_) throws IOException {
UnihexProvider unihexprovider;
try (InputStream inputstream = p_286472_.open(this.hexFile)) {
unihexprovider = this.loadData(inputstream);
}
return unihexprovider;
}
private UnihexProvider loadData(InputStream p_286795_) throws IOException {
CodepointMap<UnihexProvider.LineData> codepointmap = new CodepointMap<>(UnihexProvider.LineData[]::new, UnihexProvider.LineData[][]::new);
UnihexProvider.ReaderOutput unihexprovider$readeroutput = codepointmap::put;
UnihexProvider unihexprovider;
try (ZipInputStream zipinputstream = new ZipInputStream(p_286795_)) {
ZipEntry zipentry;
while ((zipentry = zipinputstream.getNextEntry()) != null) {
String s = zipentry.getName();
if (s.endsWith(".hex")) {
UnihexProvider.LOGGER.info("Found {}, loading", s);
UnihexProvider.readFromStream(new FastBufferedInputStream(zipinputstream), unihexprovider$readeroutput);
}
}
CodepointMap<UnihexProvider.Glyph> codepointmap1 = new CodepointMap<>(UnihexProvider.Glyph[]::new, UnihexProvider.Glyph[][]::new);
for (UnihexProvider.OverrideRange unihexprovider$overriderange : this.sizeOverrides) {
int i = unihexprovider$overriderange.from;
int j = unihexprovider$overriderange.to;
UnihexProvider.Dimensions unihexprovider$dimensions = unihexprovider$overriderange.dimensions;
for (int k = i; k <= j; k++) {
UnihexProvider.LineData unihexprovider$linedata = codepointmap.remove(k);
if (unihexprovider$linedata != null) {
codepointmap1.put(
k, new UnihexProvider.Glyph(unihexprovider$linedata, unihexprovider$dimensions.left, unihexprovider$dimensions.right)
);
}
}
}
codepointmap.forEach((p_286721_, p_286722_) -> {
int l = p_286722_.calculateWidth();
int i1 = UnihexProvider.Dimensions.left(l);
int j1 = UnihexProvider.Dimensions.right(l);
codepointmap1.put(p_286721_, new UnihexProvider.Glyph(p_286722_, i1, j1));
});
unihexprovider = new UnihexProvider(codepointmap1);
}
return unihexprovider;
}
}
@OnlyIn(Dist.CLIENT)
public record Dimensions(int left, int right) {
public static final MapCodec<UnihexProvider.Dimensions> MAP_CODEC = RecordCodecBuilder.mapCodec(
p_285497_ -> p_285497_.group(
Codec.INT.fieldOf("left").forGetter(UnihexProvider.Dimensions::left),
Codec.INT.fieldOf("right").forGetter(UnihexProvider.Dimensions::right)
)
.apply(p_285497_, UnihexProvider.Dimensions::new)
);
public static final Codec<UnihexProvider.Dimensions> CODEC = MAP_CODEC.codec();
public int pack() {
return pack(this.left, this.right);
}
public static int pack(int p_285339_, int p_285120_) {
return (p_285339_ & 0xFF) << 8 | p_285120_ & 0xFF;
}
public static int left(int p_285195_) {
return (byte)(p_285195_ >> 8);
}
public static int right(int p_285419_) {
return (byte)p_285419_;
}
}
@OnlyIn(Dist.CLIENT)
record Glyph(UnihexProvider.LineData contents, int left, int right) implements GlyphInfo {
public int width() {
return this.right - this.left + 1;
}
@Override
public float getAdvance() {
return this.width() / 2 + 1;
}
@Override
public float getShadowOffset() {
return 0.5F;
}
@Override
public float getBoldOffset() {
return 0.5F;
}
@Override
public BakedGlyph bake(Function<SheetGlyphInfo, BakedGlyph> p_285377_) {
return p_285377_.apply(
new SheetGlyphInfo() {
@Override
public float getOversample() {
return 2.0F;
}
@Override
public int getPixelWidth() {
return Glyph.this.width();
}
@Override
public int getPixelHeight() {
return 16;
}
@Override
public void upload(int p_285473_, int p_285510_, GpuTexture p_395368_) {
IntBuffer intbuffer = MemoryUtil.memAllocInt(Glyph.this.width() * 16);
UnihexProvider.unpackBitsToBytes(intbuffer, Glyph.this.contents, Glyph.this.left, Glyph.this.right);
intbuffer.rewind();
RenderSystem.getDevice()
.createCommandEncoder()
.writeToTexture(p_395368_, intbuffer, NativeImage.Format.RGBA, 0, p_285473_, p_285510_, Glyph.this.width(), 16);
MemoryUtil.memFree(intbuffer);
}
@Override
public boolean isColored() {
return true;
}
}
);
}
}
@OnlyIn(Dist.CLIENT)
record IntContents(int[] contents, int bitWidth) implements UnihexProvider.LineData {
private static final int SIZE_24 = 24;
@Override
public int line(int p_285172_) {
return this.contents[p_285172_];
}
static UnihexProvider.LineData read24(int p_285362_, ByteList p_285123_) {
int[] aint = new int[16];
int i = 0;
int j = 0;
for (int k = 0; k < 16; k++) {
int l = UnihexProvider.decodeHex(p_285362_, p_285123_, j++);
int i1 = UnihexProvider.decodeHex(p_285362_, p_285123_, j++);
int j1 = UnihexProvider.decodeHex(p_285362_, p_285123_, j++);
int k1 = UnihexProvider.decodeHex(p_285362_, p_285123_, j++);
int l1 = UnihexProvider.decodeHex(p_285362_, p_285123_, j++);
int i2 = UnihexProvider.decodeHex(p_285362_, p_285123_, j++);
int j2 = l << 20 | i1 << 16 | j1 << 12 | k1 << 8 | l1 << 4 | i2;
aint[k] = j2 << 8;
i |= j2;
}
return new UnihexProvider.IntContents(aint, 24);
}
public static UnihexProvider.LineData read32(int p_285222_, ByteList p_285346_) {
int[] aint = new int[16];
int i = 0;
int j = 0;
for (int k = 0; k < 16; k++) {
int l = UnihexProvider.decodeHex(p_285222_, p_285346_, j++);
int i1 = UnihexProvider.decodeHex(p_285222_, p_285346_, j++);
int j1 = UnihexProvider.decodeHex(p_285222_, p_285346_, j++);
int k1 = UnihexProvider.decodeHex(p_285222_, p_285346_, j++);
int l1 = UnihexProvider.decodeHex(p_285222_, p_285346_, j++);
int i2 = UnihexProvider.decodeHex(p_285222_, p_285346_, j++);
int j2 = UnihexProvider.decodeHex(p_285222_, p_285346_, j++);
int k2 = UnihexProvider.decodeHex(p_285222_, p_285346_, j++);
int l2 = l << 28 | i1 << 24 | j1 << 20 | k1 << 16 | l1 << 12 | i2 << 8 | j2 << 4 | k2;
aint[k] = l2;
i |= l2;
}
return new UnihexProvider.IntContents(aint, 32);
}
@Override
public int bitWidth() {
return this.bitWidth;
}
}
@OnlyIn(Dist.CLIENT)
public interface LineData {
int line(int p_285166_);
int bitWidth();
default int mask() {
int i = 0;
for (int j = 0; j < 16; j++) {
i |= this.line(j);
}
return i;
}
default int calculateWidth() {
int i = this.mask();
int j = this.bitWidth();
int k;
int l;
if (i == 0) {
k = 0;
l = j;
} else {
k = Integer.numberOfLeadingZeros(i);
l = 32 - Integer.numberOfTrailingZeros(i) - 1;
}
return UnihexProvider.Dimensions.pack(k, l);
}
}
@OnlyIn(Dist.CLIENT)
record OverrideRange(int from, int to, UnihexProvider.Dimensions dimensions) {
private static final Codec<UnihexProvider.OverrideRange> RAW_CODEC = RecordCodecBuilder.create(
p_285088_ -> p_285088_.group(
ExtraCodecs.CODEPOINT.fieldOf("from").forGetter(UnihexProvider.OverrideRange::from),
ExtraCodecs.CODEPOINT.fieldOf("to").forGetter(UnihexProvider.OverrideRange::to),
UnihexProvider.Dimensions.MAP_CODEC.forGetter(UnihexProvider.OverrideRange::dimensions)
)
.apply(p_285088_, UnihexProvider.OverrideRange::new)
);
public static final Codec<UnihexProvider.OverrideRange> CODEC = RAW_CODEC.validate(
p_285215_ -> p_285215_.from >= p_285215_.to
? DataResult.error(() -> "Invalid range: [" + p_285215_.from + ";" + p_285215_.to + "]")
: DataResult.success(p_285215_)
);
}
@FunctionalInterface
@OnlyIn(Dist.CLIENT)
public interface ReaderOutput {
void accept(int p_285139_, UnihexProvider.LineData p_284982_);
}
@OnlyIn(Dist.CLIENT)
record ShortContents(short[] contents) implements UnihexProvider.LineData {
@Override
public int line(int p_285158_) {
return this.contents[p_285158_] << 16;
}
static UnihexProvider.LineData read(int p_285528_, ByteList p_284958_) {
short[] ashort = new short[16];
int i = 0;
for (int j = 0; j < 16; j++) {
int k = UnihexProvider.decodeHex(p_285528_, p_284958_, i++);
int l = UnihexProvider.decodeHex(p_285528_, p_284958_, i++);
int i1 = UnihexProvider.decodeHex(p_285528_, p_284958_, i++);
int j1 = UnihexProvider.decodeHex(p_285528_, p_284958_, i++);
short short1 = (short)(k << 12 | l << 8 | i1 << 4 | j1);
ashort[j] = short1;
}
return new UnihexProvider.ShortContents(ashort);
}
@Override
public int bitWidth() {
return 16;
}
}
}