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

243 lines
9.6 KiB
Java
Raw Permalink Normal View History

2025-07-01 06:20:03 +00:00
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.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.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 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.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import org.slf4j.Logger;
@OnlyIn(Dist.CLIENT)
public class BitmapProvider implements GlyphProvider {
static final Logger LOGGER = LogUtils.getLogger();
private final NativeImage image;
private final CodepointMap<BitmapProvider.Glyph> glyphs;
BitmapProvider(NativeImage p_285380_, CodepointMap<BitmapProvider.Glyph> p_285445_) {
this.image = p_285380_;
this.glyphs = p_285445_;
}
@Override
public void close() {
this.image.close();
}
@Nullable
@Override
public GlyphInfo getGlyph(int p_232638_) {
return this.glyphs.get(p_232638_);
}
@Override
public IntSet getSupportedGlyphs() {
return IntSets.unmodifiable(this.glyphs.keySet());
}
@OnlyIn(Dist.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().xmap(p_286900_ -> {
int i = p_286900_.size();
int[][] aint = new int[i][];
for (int j = 0; j < i; j++) {
aint[j] = p_286900_.get(j).codePoints().toArray();
}
return aint;
}, p_286828_ -> {
List<String> list = new ArrayList<>(p_286828_.length);
for (int[] aint : p_286828_) {
list.add(new String(aint, 0, aint.length));
}
return list;
}).validate(BitmapProvider.Definition::validateDimensions);
public static final MapCodec<BitmapProvider.Definition> CODEC = RecordCodecBuilder.<BitmapProvider.Definition>mapCodec(
p_286905_ -> p_286905_.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(p_286905_, BitmapProvider.Definition::new)
)
.validate(BitmapProvider.Definition::validate);
private static DataResult<int[][]> validateDimensions(int[][] p_286348_) {
int i = p_286348_.length;
if (i == 0) {
return DataResult.error(() -> "Expected to find data in codepoint grid");
} else {
int[] aint = p_286348_[0];
int j = aint.length;
if (j == 0) {
return DataResult.error(() -> "Expected to find data in codepoint grid");
} else {
for (int k = 1; k < i; k++) {
int[] aint1 = p_286348_[k];
if (aint1.length != j) {
return DataResult.error(
() -> "Lines in codepoint grid have to be the same length (found: "
+ aint1.length
+ " codepoints, expected: "
+ j
+ "), pad with \\u0000"
);
}
}
return DataResult.success(p_286348_);
}
}
}
private static DataResult<BitmapProvider.Definition> validate(BitmapProvider.Definition p_286662_) {
return p_286662_.ascent > p_286662_.height
? DataResult.error(() -> "Ascent " + p_286662_.ascent + " higher than height " + p_286662_.height)
: DataResult.success(p_286662_);
}
@Override
public GlyphProviderType type() {
return GlyphProviderType.BITMAP;
}
@Override
public Either<GlyphProviderDefinition.Loader, GlyphProviderDefinition.Reference> unpack() {
return Either.left(this::load);
}
private GlyphProvider load(ResourceManager p_286694_) throws IOException {
ResourceLocation resourcelocation = this.file.withPrefix("textures/");
BitmapProvider bitmapprovider;
try (InputStream inputstream = p_286694_.open(resourcelocation)) {
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 i1 = 0; i1 < this.codepointGrid.length; i1++) {
int j1 = 0;
for (int k1 : this.codepointGrid[i1]) {
int l1 = j1++;
if (k1 != 0) {
int i2 = this.getActualGlyphWidth(nativeimage, k, l, l1, i1);
BitmapProvider.Glyph bitmapprovider$glyph = codepointmap.put(
k1, new BitmapProvider.Glyph(f, nativeimage, l1 * k, i1 * l, k, l, (int)(0.5 + i2 * f) + 1, this.ascent)
);
if (bitmapprovider$glyph != null) {
BitmapProvider.LOGGER.warn("Codepoint '{}' declared multiple times in {}", Integer.toHexString(k1), resourcelocation);
}
}
}
}
bitmapprovider = new BitmapProvider(nativeimage, codepointmap);
}
return bitmapprovider;
}
private int getActualGlyphWidth(NativeImage p_286449_, int p_286656_, int p_286554_, int p_286657_, int p_286307_) {
int i;
for (i = p_286656_ - 1; i >= 0; i--) {
int j = p_286657_ * p_286656_ + i;
for (int k = 0; k < p_286554_; k++) {
int l = p_286307_ * p_286554_ + k;
if (p_286449_.getLuminanceOrAlpha(j, l) != 0) {
return i + 1;
}
}
}
return i + 1;
}
}
@OnlyIn(Dist.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> p_232640_) {
return p_232640_.apply(
new SheetGlyphInfo() {
@Override
public float getOversample() {
return 1.0F / Glyph.this.scale;
}
@Override
public int getPixelWidth() {
return Glyph.this.width;
}
@Override
public int getPixelHeight() {
return Glyph.this.height;
}
@Override
public float getBearingTop() {
return Glyph.this.ascent;
}
@Override
public void upload(int p_232658_, int p_232659_, GpuTexture p_392194_) {
RenderSystem.getDevice()
.createCommandEncoder()
.writeToTexture(
p_392194_,
Glyph.this.image,
0,
p_232658_,
p_232659_,
Glyph.this.width,
Glyph.this.height,
Glyph.this.offsetX,
Glyph.this.offsetY
);
}
@Override
public boolean isColored() {
return Glyph.this.image.format().components() > 1;
}
}
);
}
}
}