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 glyphs; BitmapProvider(NativeImage p_285380_, CodepointMap 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 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 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 CODEC = RecordCodecBuilder.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 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 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 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 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 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; } } ); } } }