490 lines
14 KiB
Java
490 lines
14 KiB
Java
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.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.IntBuffer;
|
|
import java.util.List;
|
|
import java.util.function.Function;
|
|
import java.util.zip.ZipEntry;
|
|
import java.util.zip.ZipInputStream;
|
|
import net.fabricmc.api.EnvType;
|
|
import net.fabricmc.api.Environment;
|
|
import net.minecraft.client.gui.font.CodepointMap;
|
|
import net.minecraft.client.gui.font.glyphs.BakedGlyph;
|
|
import net.minecraft.client.gui.font.providers.GlyphProviderDefinition.Loader;
|
|
import net.minecraft.client.gui.font.providers.GlyphProviderDefinition.Reference;
|
|
import net.minecraft.client.gui.font.providers.UnihexProvider.Glyph.1;
|
|
import net.minecraft.resources.ResourceLocation;
|
|
import net.minecraft.server.packs.resources.ResourceManager;
|
|
import net.minecraft.util.ExtraCodecs;
|
|
import net.minecraft.util.FastBufferedInputStream;
|
|
import org.jetbrains.annotations.Nullable;
|
|
import org.slf4j.Logger;
|
|
|
|
@Environment(EnvType.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> glyph) {
|
|
this.glyphs = glyph;
|
|
}
|
|
|
|
@Nullable
|
|
@Override
|
|
public GlyphInfo getGlyph(int character) {
|
|
return this.glyphs.get(character);
|
|
}
|
|
|
|
@Override
|
|
public IntSet getSupportedGlyphs() {
|
|
return this.glyphs.keySet();
|
|
}
|
|
|
|
@VisibleForTesting
|
|
static void unpackBitsToBytes(IntBuffer buffer, int lineData, int left, int right) {
|
|
int i = 32 - left - 1;
|
|
int j = 32 - right - 1;
|
|
|
|
for (int k = i; k >= j; k--) {
|
|
if (k < 32 && k >= 0) {
|
|
boolean bl = (lineData >> k & 1) != 0;
|
|
buffer.put(bl ? -1 : 0);
|
|
} else {
|
|
buffer.put(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void unpackBitsToBytes(IntBuffer buffer, UnihexProvider.LineData lineData, int left, int right) {
|
|
for (int i = 0; i < 16; i++) {
|
|
int j = lineData.line(i);
|
|
unpackBitsToBytes(buffer, j, left, right);
|
|
}
|
|
}
|
|
|
|
@VisibleForTesting
|
|
static void readFromStream(InputStream stream, UnihexProvider.ReaderOutput output) throws IOException {
|
|
int i = 0;
|
|
ByteList byteList = new ByteArrayList(128);
|
|
|
|
while (true) {
|
|
boolean bl = copyUntil(stream, byteList, 58);
|
|
int j = byteList.size();
|
|
if (j == 0 && !bl) {
|
|
return;
|
|
}
|
|
|
|
if (!bl || 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(stream, byteList, 10);
|
|
int l = byteList.size();
|
|
|
|
UnihexProvider.LineData lineData = switch (l) {
|
|
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"
|
|
);
|
|
};
|
|
output.accept(k, lineData);
|
|
i++;
|
|
byteList.clear();
|
|
}
|
|
}
|
|
|
|
static int decodeHex(int lineNumber, ByteList byteList, int index) {
|
|
return decodeHex(lineNumber, byteList.getByte(index));
|
|
}
|
|
|
|
private static int decodeHex(int lineNumber, byte data) {
|
|
return switch (data) {
|
|
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 " + lineNumber + ": expected hex digit, got " + (char)data);
|
|
case 65 -> 10;
|
|
case 66 -> 11;
|
|
case 67 -> 12;
|
|
case 68 -> 13;
|
|
case 69 -> 14;
|
|
case 70 -> 15;
|
|
};
|
|
}
|
|
|
|
private static boolean copyUntil(InputStream stream, ByteList byteList, int value) throws IOException {
|
|
while (true) {
|
|
int i = stream.read();
|
|
if (i == -1) {
|
|
return false;
|
|
}
|
|
|
|
if (i == value) {
|
|
return true;
|
|
}
|
|
|
|
byteList.add((byte)i);
|
|
}
|
|
}
|
|
|
|
@Environment(EnvType.CLIENT)
|
|
record ByteContents(byte[] contents) implements UnihexProvider.LineData {
|
|
@Override
|
|
public int line(int index) {
|
|
return this.contents[index] << 24;
|
|
}
|
|
|
|
static UnihexProvider.LineData read(int index, ByteList byteList) {
|
|
byte[] bs = new byte[16];
|
|
int i = 0;
|
|
|
|
for (int j = 0; j < 16; j++) {
|
|
int k = UnihexProvider.decodeHex(index, byteList, i++);
|
|
int l = UnihexProvider.decodeHex(index, byteList, i++);
|
|
byte b = (byte)(k << 4 | l);
|
|
bs[j] = b;
|
|
}
|
|
|
|
return new UnihexProvider.ByteContents(bs);
|
|
}
|
|
|
|
@Override
|
|
public int bitWidth() {
|
|
return 8;
|
|
}
|
|
}
|
|
|
|
@Environment(EnvType.CLIENT)
|
|
public static class Definition implements GlyphProviderDefinition {
|
|
public static final MapCodec<UnihexProvider.Definition> CODEC = RecordCodecBuilder.mapCodec(
|
|
instance -> instance.group(
|
|
ResourceLocation.CODEC.fieldOf("hex_file").forGetter(definition -> definition.hexFile),
|
|
UnihexProvider.OverrideRange.CODEC.listOf().fieldOf("size_overrides").forGetter(definition -> definition.sizeOverrides)
|
|
)
|
|
.apply(instance, UnihexProvider.Definition::new)
|
|
);
|
|
private final ResourceLocation hexFile;
|
|
private final List<UnihexProvider.OverrideRange> sizeOverrides;
|
|
|
|
private Definition(ResourceLocation hexFile, List<UnihexProvider.OverrideRange> sizeOverrides) {
|
|
this.hexFile = hexFile;
|
|
this.sizeOverrides = sizeOverrides;
|
|
}
|
|
|
|
@Override
|
|
public GlyphProviderType type() {
|
|
return GlyphProviderType.UNIHEX;
|
|
}
|
|
|
|
@Override
|
|
public Either<Loader, Reference> unpack() {
|
|
return Either.left(this::load);
|
|
}
|
|
|
|
private GlyphProvider load(ResourceManager resourceManager) throws IOException {
|
|
InputStream inputStream = resourceManager.open(this.hexFile);
|
|
|
|
UnihexProvider var3;
|
|
try {
|
|
var3 = this.loadData(inputStream);
|
|
} catch (Throwable var6) {
|
|
if (inputStream != null) {
|
|
try {
|
|
inputStream.close();
|
|
} catch (Throwable var5) {
|
|
var6.addSuppressed(var5);
|
|
}
|
|
}
|
|
|
|
throw var6;
|
|
}
|
|
|
|
if (inputStream != null) {
|
|
inputStream.close();
|
|
}
|
|
|
|
return var3;
|
|
}
|
|
|
|
private UnihexProvider loadData(InputStream inputStream) throws IOException {
|
|
CodepointMap<UnihexProvider.LineData> codepointMap = new CodepointMap<>(UnihexProvider.LineData[]::new, UnihexProvider.LineData[][]::new);
|
|
UnihexProvider.ReaderOutput readerOutput = codepointMap::put;
|
|
ZipInputStream zipInputStream = new ZipInputStream(inputStream);
|
|
|
|
UnihexProvider var17;
|
|
try {
|
|
ZipEntry zipEntry;
|
|
while ((zipEntry = zipInputStream.getNextEntry()) != null) {
|
|
String string = zipEntry.getName();
|
|
if (string.endsWith(".hex")) {
|
|
UnihexProvider.LOGGER.info("Found {}, loading", string);
|
|
UnihexProvider.readFromStream(new FastBufferedInputStream(zipInputStream), readerOutput);
|
|
}
|
|
}
|
|
|
|
CodepointMap<UnihexProvider.Glyph> codepointMap2 = new CodepointMap<>(UnihexProvider.Glyph[]::new, UnihexProvider.Glyph[][]::new);
|
|
|
|
for (UnihexProvider.OverrideRange overrideRange : this.sizeOverrides) {
|
|
int i = overrideRange.from;
|
|
int j = overrideRange.to;
|
|
UnihexProvider.Dimensions dimensions = overrideRange.dimensions;
|
|
|
|
for (int k = i; k <= j; k++) {
|
|
UnihexProvider.LineData lineData = codepointMap.remove(k);
|
|
if (lineData != null) {
|
|
codepointMap2.put(k, new UnihexProvider.Glyph(lineData, dimensions.left, dimensions.right));
|
|
}
|
|
}
|
|
}
|
|
|
|
codepointMap.forEach((ix, lineDatax) -> {
|
|
int jx = lineDatax.calculateWidth();
|
|
int kx = UnihexProvider.Dimensions.left(jx);
|
|
int l = UnihexProvider.Dimensions.right(jx);
|
|
codepointMap2.put(ix, new UnihexProvider.Glyph(lineDatax, kx, l));
|
|
});
|
|
var17 = new UnihexProvider(codepointMap2);
|
|
} catch (Throwable var15) {
|
|
try {
|
|
zipInputStream.close();
|
|
} catch (Throwable var14) {
|
|
var15.addSuppressed(var14);
|
|
}
|
|
|
|
throw var15;
|
|
}
|
|
|
|
zipInputStream.close();
|
|
return var17;
|
|
}
|
|
}
|
|
|
|
@Environment(EnvType.CLIENT)
|
|
public record Dimensions(int left, int right) {
|
|
public static final MapCodec<UnihexProvider.Dimensions> MAP_CODEC = RecordCodecBuilder.mapCodec(
|
|
instance -> instance.group(
|
|
Codec.INT.fieldOf("left").forGetter(UnihexProvider.Dimensions::left), Codec.INT.fieldOf("right").forGetter(UnihexProvider.Dimensions::right)
|
|
)
|
|
.apply(instance, 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 left, int right) {
|
|
return (left & 0xFF) << 8 | right & 0xFF;
|
|
}
|
|
|
|
public static int left(int packedDimensions) {
|
|
return (byte)(packedDimensions >> 8);
|
|
}
|
|
|
|
public static int right(int packedDimensions) {
|
|
return (byte)packedDimensions;
|
|
}
|
|
}
|
|
|
|
@Environment(EnvType.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> function) {
|
|
return (BakedGlyph)function.apply(new 1(this));
|
|
}
|
|
}
|
|
|
|
@Environment(EnvType.CLIENT)
|
|
record IntContents(int[] contents, int bitWidth) implements UnihexProvider.LineData {
|
|
private static final int SIZE_24 = 24;
|
|
|
|
@Override
|
|
public int line(int index) {
|
|
return this.contents[index];
|
|
}
|
|
|
|
static UnihexProvider.LineData read24(int index, ByteList byteList) {
|
|
int[] is = new int[16];
|
|
int i = 0;
|
|
int j = 0;
|
|
|
|
for (int k = 0; k < 16; k++) {
|
|
int l = UnihexProvider.decodeHex(index, byteList, j++);
|
|
int m = UnihexProvider.decodeHex(index, byteList, j++);
|
|
int n = UnihexProvider.decodeHex(index, byteList, j++);
|
|
int o = UnihexProvider.decodeHex(index, byteList, j++);
|
|
int p = UnihexProvider.decodeHex(index, byteList, j++);
|
|
int q = UnihexProvider.decodeHex(index, byteList, j++);
|
|
int r = l << 20 | m << 16 | n << 12 | o << 8 | p << 4 | q;
|
|
is[k] = r << 8;
|
|
i |= r;
|
|
}
|
|
|
|
return new UnihexProvider.IntContents(is, 24);
|
|
}
|
|
|
|
public static UnihexProvider.LineData read32(int index, ByteList byteList) {
|
|
int[] is = new int[16];
|
|
int i = 0;
|
|
int j = 0;
|
|
|
|
for (int k = 0; k < 16; k++) {
|
|
int l = UnihexProvider.decodeHex(index, byteList, j++);
|
|
int m = UnihexProvider.decodeHex(index, byteList, j++);
|
|
int n = UnihexProvider.decodeHex(index, byteList, j++);
|
|
int o = UnihexProvider.decodeHex(index, byteList, j++);
|
|
int p = UnihexProvider.decodeHex(index, byteList, j++);
|
|
int q = UnihexProvider.decodeHex(index, byteList, j++);
|
|
int r = UnihexProvider.decodeHex(index, byteList, j++);
|
|
int s = UnihexProvider.decodeHex(index, byteList, j++);
|
|
int t = l << 28 | m << 24 | n << 20 | o << 16 | p << 12 | q << 8 | r << 4 | s;
|
|
is[k] = t;
|
|
i |= t;
|
|
}
|
|
|
|
return new UnihexProvider.IntContents(is, 32);
|
|
}
|
|
}
|
|
|
|
@Environment(EnvType.CLIENT)
|
|
public interface LineData {
|
|
int line(int index);
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
@Environment(EnvType.CLIENT)
|
|
record OverrideRange(int from, int to, UnihexProvider.Dimensions dimensions) {
|
|
private static final Codec<UnihexProvider.OverrideRange> RAW_CODEC = RecordCodecBuilder.create(
|
|
instance -> instance.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(instance, UnihexProvider.OverrideRange::new)
|
|
);
|
|
public static final Codec<UnihexProvider.OverrideRange> CODEC = RAW_CODEC.validate(
|
|
overrideRange -> overrideRange.from >= overrideRange.to
|
|
? DataResult.error(() -> "Invalid range: [" + overrideRange.from + ";" + overrideRange.to + "]")
|
|
: DataResult.success(overrideRange)
|
|
);
|
|
}
|
|
|
|
@FunctionalInterface
|
|
@Environment(EnvType.CLIENT)
|
|
public interface ReaderOutput {
|
|
void accept(int i, UnihexProvider.LineData lineData);
|
|
}
|
|
|
|
@Environment(EnvType.CLIENT)
|
|
record ShortContents(short[] contents) implements UnihexProvider.LineData {
|
|
@Override
|
|
public int line(int index) {
|
|
return this.contents[index] << 16;
|
|
}
|
|
|
|
static UnihexProvider.LineData read(int index, ByteList byteList) {
|
|
short[] ss = new short[16];
|
|
int i = 0;
|
|
|
|
for (int j = 0; j < 16; j++) {
|
|
int k = UnihexProvider.decodeHex(index, byteList, i++);
|
|
int l = UnihexProvider.decodeHex(index, byteList, i++);
|
|
int m = UnihexProvider.decodeHex(index, byteList, i++);
|
|
int n = UnihexProvider.decodeHex(index, byteList, i++);
|
|
short s = (short)(k << 12 | l << 8 | m << 4 | n);
|
|
ss[j] = s;
|
|
}
|
|
|
|
return new UnihexProvider.ShortContents(ss);
|
|
}
|
|
|
|
@Override
|
|
public int bitWidth() {
|
|
return 16;
|
|
}
|
|
}
|
|
}
|