466 lines
18 KiB
Java
466 lines
18 KiB
Java
/*
|
|
* Decompiled with CFR 0.152.
|
|
*
|
|
* Could not load the following classes:
|
|
* com.google.common.annotations.VisibleForTesting
|
|
* com.mojang.datafixers.kinds.App
|
|
* com.mojang.datafixers.kinds.Applicative
|
|
* com.mojang.datafixers.util.Either
|
|
* com.mojang.logging.LogUtils
|
|
* com.mojang.serialization.Codec
|
|
* com.mojang.serialization.DataResult
|
|
* com.mojang.serialization.MapCodec
|
|
* com.mojang.serialization.codecs.RecordCodecBuilder
|
|
* it.unimi.dsi.fastutil.bytes.ByteArrayList
|
|
* it.unimi.dsi.fastutil.bytes.ByteList
|
|
* it.unimi.dsi.fastutil.ints.IntSet
|
|
* org.jspecify.annotations.Nullable
|
|
* org.lwjgl.system.MemoryUtil
|
|
* org.slf4j.Logger
|
|
*/
|
|
package net.minecraft.client.gui.font.providers;
|
|
|
|
import com.google.common.annotations.VisibleForTesting;
|
|
import com.mojang.blaze3d.font.GlyphBitmap;
|
|
import com.mojang.blaze3d.font.GlyphInfo;
|
|
import com.mojang.blaze3d.font.GlyphProvider;
|
|
import com.mojang.blaze3d.font.UnbakedGlyph;
|
|
import com.mojang.blaze3d.platform.NativeImage;
|
|
import com.mojang.blaze3d.systems.RenderSystem;
|
|
import com.mojang.blaze3d.textures.GpuTexture;
|
|
import com.mojang.datafixers.kinds.App;
|
|
import com.mojang.datafixers.kinds.Applicative;
|
|
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 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.Buffer;
|
|
import java.nio.IntBuffer;
|
|
import java.util.List;
|
|
import java.util.zip.ZipEntry;
|
|
import java.util.zip.ZipInputStream;
|
|
import net.minecraft.client.gui.font.CodepointMap;
|
|
import net.minecraft.client.gui.font.glyphs.BakedGlyph;
|
|
import net.minecraft.client.gui.font.providers.GlyphProviderDefinition;
|
|
import net.minecraft.client.gui.font.providers.GlyphProviderType;
|
|
import net.minecraft.resources.Identifier;
|
|
import net.minecraft.server.packs.resources.ResourceManager;
|
|
import net.minecraft.util.ExtraCodecs;
|
|
import net.minecraft.util.FastBufferedInputStream;
|
|
import org.jspecify.annotations.Nullable;
|
|
import org.lwjgl.system.MemoryUtil;
|
|
import org.slf4j.Logger;
|
|
|
|
public class UnihexProvider
|
|
implements GlyphProvider {
|
|
private 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<Glyph> glyphs;
|
|
|
|
private UnihexProvider(CodepointMap<Glyph> glyphs) {
|
|
this.glyphs = glyphs;
|
|
}
|
|
|
|
@Override
|
|
public @Nullable UnbakedGlyph getGlyph(int codepoint) {
|
|
return this.glyphs.get(codepoint);
|
|
}
|
|
|
|
@Override
|
|
public IntSet getSupportedGlyphs() {
|
|
return this.glyphs.keySet();
|
|
}
|
|
|
|
@VisibleForTesting
|
|
static void unpackBitsToBytes(IntBuffer output, int value, int left, int right) {
|
|
int startBit = 32 - left - 1;
|
|
int endBit = 32 - right - 1;
|
|
for (int i = startBit; i >= endBit; --i) {
|
|
if (i >= 32 || i < 0) {
|
|
output.put(0);
|
|
continue;
|
|
}
|
|
boolean isSet = (value >> i & 1) != 0;
|
|
output.put(isSet ? -1 : 0);
|
|
}
|
|
}
|
|
|
|
private static void unpackBitsToBytes(IntBuffer output, LineData data, int left, int right) {
|
|
for (int i = 0; i < 16; ++i) {
|
|
int line = data.line(i);
|
|
UnihexProvider.unpackBitsToBytes(output, line, left, right);
|
|
}
|
|
}
|
|
|
|
@VisibleForTesting
|
|
static void readFromStream(InputStream input, ReaderOutput output) throws IOException {
|
|
int line = 0;
|
|
ByteArrayList buffer = new ByteArrayList(128);
|
|
while (true) {
|
|
boolean foundColon = UnihexProvider.copyUntil(input, (ByteList)buffer, 58);
|
|
int codepointDigitCount = buffer.size();
|
|
if (codepointDigitCount == 0 && !foundColon) break;
|
|
if (!foundColon || codepointDigitCount != 4 && codepointDigitCount != 5 && codepointDigitCount != 6) {
|
|
throw new IllegalArgumentException("Invalid entry at line " + line + ": expected 4, 5 or 6 hex digits followed by a colon");
|
|
}
|
|
int codepoint = 0;
|
|
for (int i = 0; i < codepointDigitCount; ++i) {
|
|
codepoint = codepoint << 4 | UnihexProvider.decodeHex(line, buffer.getByte(i));
|
|
}
|
|
buffer.clear();
|
|
UnihexProvider.copyUntil(input, (ByteList)buffer, 10);
|
|
int dataDigitCount = buffer.size();
|
|
LineData contents = switch (dataDigitCount) {
|
|
case 32 -> ByteContents.read(line, (ByteList)buffer);
|
|
case 64 -> ShortContents.read(line, (ByteList)buffer);
|
|
case 96 -> IntContents.read24(line, (ByteList)buffer);
|
|
case 128 -> IntContents.read32(line, (ByteList)buffer);
|
|
default -> throw new IllegalArgumentException("Invalid entry at line " + line + ": expected hex number describing (8,16,24,32) x 16 bitmap, followed by a new line");
|
|
};
|
|
output.accept(codepoint, contents);
|
|
++line;
|
|
buffer.clear();
|
|
}
|
|
}
|
|
|
|
private static int decodeHex(int line, ByteList input, int index) {
|
|
return UnihexProvider.decodeHex(line, input.getByte(index));
|
|
}
|
|
|
|
private static int decodeHex(int line, byte b) {
|
|
return switch (b) {
|
|
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;
|
|
case 65 -> 10;
|
|
case 66 -> 11;
|
|
case 67 -> 12;
|
|
case 68 -> 13;
|
|
case 69 -> 14;
|
|
case 70 -> 15;
|
|
default -> throw new IllegalArgumentException("Invalid entry at line " + line + ": expected hex digit, got " + (char)b);
|
|
};
|
|
}
|
|
|
|
private static boolean copyUntil(InputStream input, ByteList output, int delimiter) throws IOException {
|
|
int b;
|
|
while ((b = input.read()) != -1) {
|
|
if (b == delimiter) {
|
|
return true;
|
|
}
|
|
output.add((byte)b);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public static interface LineData {
|
|
public int line(int var1);
|
|
|
|
public int bitWidth();
|
|
|
|
default public int mask() {
|
|
int mask = 0;
|
|
for (int i = 0; i < 16; ++i) {
|
|
mask |= this.line(i);
|
|
}
|
|
return mask;
|
|
}
|
|
|
|
default public int calculateWidth() {
|
|
int right;
|
|
int left;
|
|
int mask = this.mask();
|
|
int bitWidth = this.bitWidth();
|
|
if (mask == 0) {
|
|
left = 0;
|
|
right = bitWidth;
|
|
} else {
|
|
left = Integer.numberOfLeadingZeros(mask);
|
|
right = 32 - Integer.numberOfTrailingZeros(mask) - 1;
|
|
}
|
|
return Dimensions.pack(left, right);
|
|
}
|
|
}
|
|
|
|
private record ByteContents(byte[] contents) implements LineData
|
|
{
|
|
@Override
|
|
public int line(int index) {
|
|
return this.contents[index] << 24;
|
|
}
|
|
|
|
private static LineData read(int line, ByteList input) {
|
|
byte[] content = new byte[16];
|
|
int pos = 0;
|
|
for (int i = 0; i < 16; ++i) {
|
|
byte v;
|
|
int n1 = UnihexProvider.decodeHex(line, input, pos++);
|
|
int n0 = UnihexProvider.decodeHex(line, input, pos++);
|
|
content[i] = v = (byte)(n1 << 4 | n0);
|
|
}
|
|
return new ByteContents(content);
|
|
}
|
|
|
|
@Override
|
|
public int bitWidth() {
|
|
return 8;
|
|
}
|
|
}
|
|
|
|
private record ShortContents(short[] contents) implements LineData
|
|
{
|
|
@Override
|
|
public int line(int index) {
|
|
return this.contents[index] << 16;
|
|
}
|
|
|
|
private static LineData read(int line, ByteList input) {
|
|
short[] content = new short[16];
|
|
int pos = 0;
|
|
for (int i = 0; i < 16; ++i) {
|
|
short v;
|
|
int n3 = UnihexProvider.decodeHex(line, input, pos++);
|
|
int n2 = UnihexProvider.decodeHex(line, input, pos++);
|
|
int n1 = UnihexProvider.decodeHex(line, input, pos++);
|
|
int n0 = UnihexProvider.decodeHex(line, input, pos++);
|
|
content[i] = v = (short)(n3 << 12 | n2 << 8 | n1 << 4 | n0);
|
|
}
|
|
return new ShortContents(content);
|
|
}
|
|
|
|
@Override
|
|
public int bitWidth() {
|
|
return 16;
|
|
}
|
|
}
|
|
|
|
private record IntContents(int[] contents, int bitWidth) implements LineData
|
|
{
|
|
private static final int SIZE_24 = 24;
|
|
|
|
@Override
|
|
public int line(int index) {
|
|
return this.contents[index];
|
|
}
|
|
|
|
private static LineData read24(int line, ByteList input) {
|
|
int[] content = new int[16];
|
|
int mask = 0;
|
|
int pos = 0;
|
|
for (int i = 0; i < 16; ++i) {
|
|
int n5 = UnihexProvider.decodeHex(line, input, pos++);
|
|
int n4 = UnihexProvider.decodeHex(line, input, pos++);
|
|
int n3 = UnihexProvider.decodeHex(line, input, pos++);
|
|
int n2 = UnihexProvider.decodeHex(line, input, pos++);
|
|
int n1 = UnihexProvider.decodeHex(line, input, pos++);
|
|
int n0 = UnihexProvider.decodeHex(line, input, pos++);
|
|
int v = n5 << 20 | n4 << 16 | n3 << 12 | n2 << 8 | n1 << 4 | n0;
|
|
content[i] = v << 8;
|
|
mask |= v;
|
|
}
|
|
return new IntContents(content, 24);
|
|
}
|
|
|
|
public static LineData read32(int line, ByteList input) {
|
|
int[] content = new int[16];
|
|
int mask = 0;
|
|
int pos = 0;
|
|
for (int i = 0; i < 16; ++i) {
|
|
int v;
|
|
int n7 = UnihexProvider.decodeHex(line, input, pos++);
|
|
int n6 = UnihexProvider.decodeHex(line, input, pos++);
|
|
int n5 = UnihexProvider.decodeHex(line, input, pos++);
|
|
int n4 = UnihexProvider.decodeHex(line, input, pos++);
|
|
int n3 = UnihexProvider.decodeHex(line, input, pos++);
|
|
int n2 = UnihexProvider.decodeHex(line, input, pos++);
|
|
int n1 = UnihexProvider.decodeHex(line, input, pos++);
|
|
int n0 = UnihexProvider.decodeHex(line, input, pos++);
|
|
content[i] = v = n7 << 28 | n6 << 24 | n5 << 20 | n4 << 16 | n3 << 12 | n2 << 8 | n1 << 4 | n0;
|
|
mask |= v;
|
|
}
|
|
return new IntContents(content, 32);
|
|
}
|
|
}
|
|
|
|
@FunctionalInterface
|
|
public static interface ReaderOutput {
|
|
public void accept(int var1, LineData var2);
|
|
}
|
|
|
|
private record Glyph(LineData contents, int left, int right) implements UnbakedGlyph
|
|
{
|
|
public int width() {
|
|
return this.right - this.left + 1;
|
|
}
|
|
|
|
@Override
|
|
public GlyphInfo info() {
|
|
return new GlyphInfo(){
|
|
|
|
@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(UnbakedGlyph.Stitcher stitcher) {
|
|
return stitcher.stitch(this.info(), new GlyphBitmap(){
|
|
|
|
@Override
|
|
public float getOversample() {
|
|
return 2.0f;
|
|
}
|
|
|
|
@Override
|
|
public int getPixelWidth() {
|
|
return this.width();
|
|
}
|
|
|
|
@Override
|
|
public int getPixelHeight() {
|
|
return 16;
|
|
}
|
|
|
|
@Override
|
|
public void upload(int x, int y, GpuTexture texture) {
|
|
IntBuffer targetBuffer = MemoryUtil.memAllocInt((int)(this.width() * 16));
|
|
UnihexProvider.unpackBitsToBytes(targetBuffer, contents, left, right);
|
|
targetBuffer.rewind();
|
|
RenderSystem.getDevice().createCommandEncoder().writeToTexture(texture, MemoryUtil.memByteBuffer((IntBuffer)targetBuffer), NativeImage.Format.RGBA, 0, 0, x, y, this.width(), 16);
|
|
MemoryUtil.memFree((Buffer)targetBuffer);
|
|
}
|
|
|
|
@Override
|
|
public boolean isColored() {
|
|
return true;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
public static class Definition
|
|
implements GlyphProviderDefinition {
|
|
public static final MapCodec<Definition> CODEC = RecordCodecBuilder.mapCodec(i -> i.group((App)Identifier.CODEC.fieldOf("hex_file").forGetter(o -> o.hexFile), (App)OverrideRange.CODEC.listOf().optionalFieldOf("size_overrides", List.of()).forGetter(o -> o.sizeOverrides)).apply((Applicative)i, Definition::new));
|
|
private final Identifier hexFile;
|
|
private final List<OverrideRange> sizeOverrides;
|
|
|
|
private Definition(Identifier hexFile, List<OverrideRange> sizeOverrides) {
|
|
this.hexFile = hexFile;
|
|
this.sizeOverrides = sizeOverrides;
|
|
}
|
|
|
|
@Override
|
|
public GlyphProviderType type() {
|
|
return GlyphProviderType.UNIHEX;
|
|
}
|
|
|
|
@Override
|
|
public Either<GlyphProviderDefinition.Loader, GlyphProviderDefinition.Reference> unpack() {
|
|
return Either.left(this::load);
|
|
}
|
|
|
|
private GlyphProvider load(ResourceManager resourceManager) throws IOException {
|
|
try (InputStream raw = resourceManager.open(this.hexFile);){
|
|
UnihexProvider unihexProvider = this.loadData(raw);
|
|
return unihexProvider;
|
|
}
|
|
}
|
|
|
|
private UnihexProvider loadData(InputStream zipFile) throws IOException {
|
|
CodepointMap<LineData> bits = new CodepointMap<LineData>(LineData[]::new, x$0 -> new LineData[x$0][]);
|
|
ReaderOutput output = bits::put;
|
|
try (ZipInputStream zis = new ZipInputStream(zipFile);){
|
|
ZipEntry entry;
|
|
while ((entry = zis.getNextEntry()) != null) {
|
|
String name = entry.getName();
|
|
if (!name.endsWith(".hex")) continue;
|
|
LOGGER.info("Found {}, loading", (Object)name);
|
|
UnihexProvider.readFromStream(new FastBufferedInputStream(zis), output);
|
|
}
|
|
CodepointMap<Glyph> glyphs = new CodepointMap<Glyph>(Glyph[]::new, x$0 -> new Glyph[x$0][]);
|
|
for (OverrideRange sizeOverride : this.sizeOverrides) {
|
|
int from = sizeOverride.from;
|
|
int to = sizeOverride.to;
|
|
Dimensions size = sizeOverride.dimensions;
|
|
for (int c = from; c <= to; ++c) {
|
|
LineData codepointBits = (LineData)bits.remove(c);
|
|
if (codepointBits == null) continue;
|
|
glyphs.put(c, new Glyph(codepointBits, size.left, size.right));
|
|
}
|
|
}
|
|
bits.forEach((codepoint, glyphBits) -> {
|
|
int packedSize = glyphBits.calculateWidth();
|
|
int left = Dimensions.left(packedSize);
|
|
int right = Dimensions.right(packedSize);
|
|
glyphs.put(codepoint, new Glyph((LineData)glyphBits, left, right));
|
|
});
|
|
UnihexProvider unihexProvider = new UnihexProvider(glyphs);
|
|
return unihexProvider;
|
|
}
|
|
}
|
|
}
|
|
|
|
public record Dimensions(int left, int right) {
|
|
public static final MapCodec<Dimensions> MAP_CODEC = RecordCodecBuilder.mapCodec(i -> i.group((App)Codec.INT.fieldOf("left").forGetter(Dimensions::left), (App)Codec.INT.fieldOf("right").forGetter(Dimensions::right)).apply((Applicative)i, Dimensions::new));
|
|
public static final Codec<Dimensions> CODEC = MAP_CODEC.codec();
|
|
|
|
public int pack() {
|
|
return Dimensions.pack(this.left, this.right);
|
|
}
|
|
|
|
public static int pack(int left, int right) {
|
|
return (left & 0xFF) << 8 | right & 0xFF;
|
|
}
|
|
|
|
public static int left(int packed) {
|
|
return (byte)(packed >> 8);
|
|
}
|
|
|
|
public static int right(int packed) {
|
|
return (byte)packed;
|
|
}
|
|
}
|
|
|
|
private record OverrideRange(int from, int to, Dimensions dimensions) {
|
|
private static final Codec<OverrideRange> RAW_CODEC = RecordCodecBuilder.create(i -> i.group((App)ExtraCodecs.CODEPOINT.fieldOf("from").forGetter(OverrideRange::from), (App)ExtraCodecs.CODEPOINT.fieldOf("to").forGetter(OverrideRange::to), (App)Dimensions.MAP_CODEC.forGetter(OverrideRange::dimensions)).apply((Applicative)i, OverrideRange::new));
|
|
public static final Codec<OverrideRange> CODEC = RAW_CODEC.validate(o -> {
|
|
if (o.from >= o.to) {
|
|
return DataResult.error(() -> "Invalid range: [" + o.from + ";" + o.to + "]");
|
|
}
|
|
return DataResult.success((Object)o);
|
|
});
|
|
}
|
|
}
|
|
|