minecraft-src/net/minecraft/client/gui/font/providers/BitmapProvider.java
2025-07-04 03:15:13 +03:00

209 lines
6.6 KiB
Java

package net.minecraft.client.gui.font.providers;
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.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.ints.IntSet;
import it.unimi.dsi.fastutil.ints.IntSets;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
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.BitmapProvider.Glyph.1;
import net.minecraft.client.gui.font.providers.GlyphProviderDefinition.Loader;
import net.minecraft.client.gui.font.providers.GlyphProviderDefinition.Reference;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
@Environment(EnvType.CLIENT)
public class BitmapProvider implements GlyphProvider {
static final Logger LOGGER = LogUtils.getLogger();
private final NativeImage image;
private final CodepointMap<BitmapProvider.Glyph> glyphs;
BitmapProvider(NativeImage image, CodepointMap<BitmapProvider.Glyph> glyphs) {
this.image = image;
this.glyphs = glyphs;
}
@Override
public void close() {
this.image.close();
}
@Nullable
@Override
public GlyphInfo getGlyph(int character) {
return this.glyphs.get(character);
}
@Override
public IntSet getSupportedGlyphs() {
return IntSets.unmodifiable(this.glyphs.keySet());
}
@Environment(EnvType.CLIENT)
public record Definition(ResourceLocation file, int height, int ascent, int[][] codepointGrid) implements GlyphProviderDefinition {
private static final Codec<int[][]> CODEPOINT_GRID_CODEC = Codec.STRING.listOf().<int[][]>xmap(list -> {
int i = list.size();
int[][] is = new int[i][];
for (int j = 0; j < i; j++) {
is[j] = ((String)list.get(j)).codePoints().toArray();
}
return is;
}, is -> {
List<String> list = new ArrayList(is.length);
for (int[] js : is) {
list.add(new String(js, 0, js.length));
}
return list;
}).validate(BitmapProvider.Definition::validateDimensions);
public static final MapCodec<BitmapProvider.Definition> CODEC = RecordCodecBuilder.<BitmapProvider.Definition>mapCodec(
instance -> instance.group(
ResourceLocation.CODEC.fieldOf("file").forGetter(BitmapProvider.Definition::file),
Codec.INT.optionalFieldOf("height", 8).forGetter(BitmapProvider.Definition::height),
Codec.INT.fieldOf("ascent").forGetter(BitmapProvider.Definition::ascent),
CODEPOINT_GRID_CODEC.fieldOf("chars").forGetter(BitmapProvider.Definition::codepointGrid)
)
.apply(instance, BitmapProvider.Definition::new)
)
.validate(BitmapProvider.Definition::validate);
private static DataResult<int[][]> validateDimensions(int[][] dimensions) {
int i = dimensions.length;
if (i == 0) {
return DataResult.error(() -> "Expected to find data in codepoint grid");
} else {
int[] is = dimensions[0];
int j = is.length;
if (j == 0) {
return DataResult.error(() -> "Expected to find data in codepoint grid");
} else {
for (int k = 1; k < i; k++) {
int[] js = dimensions[k];
if (js.length != j) {
return DataResult.error(
() -> "Lines in codepoint grid have to be the same length (found: " + js.length + " codepoints, expected: " + j + "), pad with \\u0000"
);
}
}
return DataResult.success(dimensions);
}
}
}
private static DataResult<BitmapProvider.Definition> validate(BitmapProvider.Definition definition) {
return definition.ascent > definition.height
? DataResult.error(() -> "Ascent " + definition.ascent + " higher than height " + definition.height)
: DataResult.success(definition);
}
@Override
public GlyphProviderType type() {
return GlyphProviderType.BITMAP;
}
@Override
public Either<Loader, Reference> unpack() {
return Either.left(this::load);
}
private GlyphProvider load(ResourceManager resourceManager) throws IOException {
ResourceLocation resourceLocation = this.file.withPrefix("textures/");
InputStream inputStream = resourceManager.open(resourceLocation);
BitmapProvider var22;
try {
NativeImage nativeImage = NativeImage.read(NativeImage.Format.RGBA, inputStream);
int i = nativeImage.getWidth();
int j = nativeImage.getHeight();
int k = i / this.codepointGrid[0].length;
int l = j / this.codepointGrid.length;
float f = (float)this.height / l;
CodepointMap<BitmapProvider.Glyph> codepointMap = new CodepointMap<>(BitmapProvider.Glyph[]::new, BitmapProvider.Glyph[][]::new);
for (int m = 0; m < this.codepointGrid.length; m++) {
int n = 0;
for (int o : this.codepointGrid[m]) {
int p = n++;
if (o != 0) {
int q = this.getActualGlyphWidth(nativeImage, k, l, p, m);
BitmapProvider.Glyph glyph = codepointMap.put(o, new BitmapProvider.Glyph(f, nativeImage, p * k, m * l, k, l, (int)(0.5 + q * f) + 1, this.ascent));
if (glyph != null) {
BitmapProvider.LOGGER.warn("Codepoint '{}' declared multiple times in {}", Integer.toHexString(o), resourceLocation);
}
}
}
}
var22 = new BitmapProvider(nativeImage, codepointMap);
} catch (Throwable var21) {
if (inputStream != null) {
try {
inputStream.close();
} catch (Throwable var20) {
var21.addSuppressed(var20);
}
}
throw var21;
}
if (inputStream != null) {
inputStream.close();
}
return var22;
}
private int getActualGlyphWidth(NativeImage image, int width, int height, int x, int y) {
int i;
for (i = width - 1; i >= 0; i--) {
int j = x * width + i;
for (int k = 0; k < height; k++) {
int l = y * height + k;
if (image.getLuminanceOrAlpha(j, l) != 0) {
return i + 1;
}
}
}
return i + 1;
}
}
@Environment(EnvType.CLIENT)
record Glyph(float scale, NativeImage image, int offsetX, int offsetY, int width, int height, int advance, int ascent) implements GlyphInfo {
@Override
public float getAdvance() {
return this.advance;
}
@Override
public BakedGlyph bake(Function<SheetGlyphInfo, BakedGlyph> function) {
return (BakedGlyph)function.apply(new 1(this));
}
}
}