/* * Decompiled with CFR 0.152. * * Could not load the following classes: * com.google.common.collect.Sets * it.unimi.dsi.fastutil.ints.Int2ObjectMap * it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap * it.unimi.dsi.fastutil.ints.IntArrayList * it.unimi.dsi.fastutil.ints.IntCollection * it.unimi.dsi.fastutil.ints.IntList * it.unimi.dsi.fastutil.ints.IntOpenHashSet * org.jspecify.annotations.Nullable */ package net.minecraft.client.gui.font; import com.google.common.collect.Sets; 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 it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntCollection; import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.function.IntFunction; import java.util.function.Supplier; import net.minecraft.client.gui.GlyphSource; import net.minecraft.client.gui.font.CodepointMap; import net.minecraft.client.gui.font.FontOption; import net.minecraft.client.gui.font.GlyphStitcher; import net.minecraft.client.gui.font.glyphs.BakedGlyph; import net.minecraft.client.gui.font.glyphs.EffectGlyph; import net.minecraft.client.gui.font.glyphs.SpecialGlyphs; import net.minecraft.network.chat.Style; import net.minecraft.util.Mth; import net.minecraft.util.RandomSource; import org.jspecify.annotations.Nullable; public class FontSet implements AutoCloseable { private static final float LARGE_FORWARD_ADVANCE = 32.0f; private static final BakedGlyph INVISIBLE_MISSING_GLYPH = new BakedGlyph(){ @Override public GlyphInfo info() { return SpecialGlyphs.MISSING; } @Override public @Nullable TextRenderable.Styled createGlyph(float x, float y, int color, int shadowColor, Style style, float boldOffset, float shadowOffset) { return null; } }; private final GlyphStitcher stitcher; private final UnbakedGlyph.Stitcher wrappedStitcher = new UnbakedGlyph.Stitcher(){ @Override public BakedGlyph stitch(GlyphInfo glyphInfo, GlyphBitmap glyphBitmap) { return Objects.requireNonNullElse(FontSet.this.stitcher.stitch(glyphInfo, glyphBitmap), FontSet.this.missingGlyph); } @Override public BakedGlyph getMissing() { return FontSet.this.missingGlyph; } }; private List allProviders = List.of(); private List activeProviders = List.of(); private final Int2ObjectMap glyphsByWidth = new Int2ObjectOpenHashMap(); private final CodepointMap glyphCache = new CodepointMap(SelectedGlyphs[]::new, x$0 -> new SelectedGlyphs[x$0][]); private final IntFunction glyphGetter = this::computeGlyphInfo; private BakedGlyph missingGlyph = INVISIBLE_MISSING_GLYPH; private final Supplier missingGlyphGetter = () -> this.missingGlyph; private final SelectedGlyphs missingSelectedGlyphs = new SelectedGlyphs(this.missingGlyphGetter, this.missingGlyphGetter); private @Nullable EffectGlyph whiteGlyph; private final GlyphSource anyGlyphs = new Source(false); private final GlyphSource nonFishyGlyphs = new Source(true); public FontSet(GlyphStitcher stitcher) { this.stitcher = stitcher; } public void reload(List providers, Set options) { this.allProviders = providers; this.reload(options); } public void reload(Set options) { this.activeProviders = List.of(); this.resetTextures(); this.activeProviders = this.selectProviders(this.allProviders, options); } private void resetTextures() { this.stitcher.reset(); this.glyphCache.clear(); this.glyphsByWidth.clear(); this.missingGlyph = Objects.requireNonNull(SpecialGlyphs.MISSING.bake(this.stitcher)); this.whiteGlyph = SpecialGlyphs.WHITE.bake(this.stitcher); } private List selectProviders(List providers, Set options) { IntOpenHashSet supportedGlyphs = new IntOpenHashSet(); ArrayList selectedProviders = new ArrayList(); for (GlyphProvider.Conditional conditionalProvider : providers) { if (!conditionalProvider.filter().apply(options)) continue; selectedProviders.add(conditionalProvider.provider()); supportedGlyphs.addAll((IntCollection)conditionalProvider.provider().getSupportedGlyphs()); } HashSet usedProviders = Sets.newHashSet(); supportedGlyphs.forEach(codepoint -> { for (GlyphProvider provider : selectedProviders) { UnbakedGlyph glyph = provider.getGlyph(codepoint); if (glyph == null) continue; usedProviders.add(provider); if (glyph.info() == SpecialGlyphs.MISSING) break; ((IntList)this.glyphsByWidth.computeIfAbsent(Mth.ceil(glyph.info().getAdvance(false)), w -> new IntArrayList())).add(codepoint); break; } }); return selectedProviders.stream().filter(usedProviders::contains).toList(); } @Override public void close() { this.stitcher.close(); } private static boolean hasFishyAdvance(GlyphInfo glyph) { float advance = glyph.getAdvance(false); if (advance < 0.0f || advance > 32.0f) { return true; } float boldAdvance = glyph.getAdvance(true); return boldAdvance < 0.0f || boldAdvance > 32.0f; } private SelectedGlyphs computeGlyphInfo(int codepoint) { DelayedBake firstGlyph = null; for (GlyphProvider provider : this.activeProviders) { UnbakedGlyph glyph = provider.getGlyph(codepoint); if (glyph == null) continue; if (firstGlyph == null) { firstGlyph = new DelayedBake(glyph); } if (FontSet.hasFishyAdvance(glyph.info())) continue; if (firstGlyph.unbaked == glyph) { return new SelectedGlyphs(firstGlyph, firstGlyph); } return new SelectedGlyphs(firstGlyph, new DelayedBake(glyph)); } if (firstGlyph != null) { return new SelectedGlyphs(firstGlyph, this.missingGlyphGetter); } return this.missingSelectedGlyphs; } private SelectedGlyphs getGlyph(int codepoint) { return this.glyphCache.computeIfAbsent(codepoint, this.glyphGetter); } public BakedGlyph getRandomGlyph(RandomSource random, int width) { IntList chars = (IntList)this.glyphsByWidth.get(width); if (chars != null && !chars.isEmpty()) { return this.getGlyph(chars.getInt(random.nextInt(chars.size()))).nonFishy().get(); } return this.missingGlyph; } public EffectGlyph whiteGlyph() { return Objects.requireNonNull(this.whiteGlyph); } public GlyphSource source(boolean nonFishyOnly) { return nonFishyOnly ? this.nonFishyGlyphs : this.anyGlyphs; } private record SelectedGlyphs(Supplier any, Supplier nonFishy) { private Supplier select(boolean filterFishy) { return filterFishy ? this.nonFishy : this.any; } } public class Source implements GlyphSource { private final boolean filterFishyGlyphs; public Source(boolean filterFishyGlyphs) { this.filterFishyGlyphs = filterFishyGlyphs; } @Override public BakedGlyph getGlyph(int codepoint) { return FontSet.this.getGlyph(codepoint).select(this.filterFishyGlyphs).get(); } @Override public BakedGlyph getRandomGlyph(RandomSource random, int width) { return FontSet.this.getRandomGlyph(random, width); } } private class DelayedBake implements Supplier { private final UnbakedGlyph unbaked; private @Nullable BakedGlyph baked; private DelayedBake(UnbakedGlyph unbaked) { this.unbaked = unbaked; } @Override public BakedGlyph get() { if (this.baked == null) { this.baked = this.unbaked.bake(FontSet.this.wrappedStitcher); } return this.baked; } } }