|
| 1 | +package net.minecraft.client.gui.font; |
| 2 | + |
| 3 | +import com.google.common.collect.Sets; |
| 4 | +import com.mojang.blaze3d.font.GlyphBitmap; |
| 5 | +import com.mojang.blaze3d.font.GlyphInfo; |
| 6 | +import com.mojang.blaze3d.font.GlyphProvider; |
| 7 | +import com.mojang.blaze3d.font.UnbakedGlyph; |
| 8 | +import it.unimi.dsi.fastutil.ints.Int2ObjectFunction; |
| 9 | +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; |
| 10 | +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; |
| 11 | +import it.unimi.dsi.fastutil.ints.IntArrayList; |
| 12 | +import it.unimi.dsi.fastutil.ints.IntList; |
| 13 | +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; |
| 14 | +import it.unimi.dsi.fastutil.ints.IntSet; |
| 15 | +import java.util.ArrayList; |
| 16 | +import java.util.List; |
| 17 | +import java.util.Objects; |
| 18 | +import java.util.Set; |
| 19 | +import java.util.function.IntFunction; |
| 20 | +import java.util.function.Supplier; |
| 21 | +import net.fabricmc.api.EnvType; |
| 22 | +import net.fabricmc.api.Environment; |
| 23 | +import net.minecraft.client.gui.GlyphSource; |
| 24 | +import net.minecraft.client.gui.font.glyphs.BakedGlyph; |
| 25 | +import net.minecraft.client.gui.font.glyphs.EffectGlyph; |
| 26 | +import net.minecraft.client.gui.font.glyphs.SpecialGlyphs; |
| 27 | +import net.minecraft.network.chat.Style; |
| 28 | +import net.minecraft.util.Mth; |
| 29 | +import net.minecraft.util.RandomSource; |
| 30 | +import org.jspecify.annotations.Nullable; |
| 31 | + |
| 32 | +@Environment(EnvType.CLIENT) |
| 33 | +public class FontSet implements AutoCloseable { |
| 34 | + private static final float LARGE_FORWARD_ADVANCE = 32.0F; |
| 35 | + private static final BakedGlyph INVISIBLE_MISSING_GLYPH = new BakedGlyph() { |
| 36 | + @Override |
| 37 | + public GlyphInfo info() { |
| 38 | + return SpecialGlyphs.MISSING; |
| 39 | + } |
| 40 | + |
| 41 | + @Nullable |
| 42 | + @Override |
| 43 | + public TextRenderable.Styled createGlyph(float f, float g, int i, int j, Style style, float h, float k) { |
| 44 | + return null; |
| 45 | + } |
| 46 | + }; |
| 47 | + final GlyphStitcher stitcher; |
| 48 | + final UnbakedGlyph.Stitcher wrappedStitcher = new UnbakedGlyph.Stitcher() { |
| 49 | + @Override |
| 50 | + public BakedGlyph stitch(GlyphInfo glyphInfo, GlyphBitmap glyphBitmap) { |
| 51 | + return (BakedGlyph)Objects.requireNonNullElse(FontSet.this.stitcher.stitch(glyphInfo, glyphBitmap), FontSet.this.missingGlyph); |
| 52 | + } |
| 53 | + |
| 54 | + @Override |
| 55 | + public BakedGlyph getMissing() { |
| 56 | + return FontSet.this.missingGlyph; |
| 57 | + } |
| 58 | + }; |
| 59 | + private List<GlyphProvider.Conditional> allProviders = List.of(); |
| 60 | + private List<GlyphProvider> activeProviders = List.of(); |
| 61 | + private final Int2ObjectMap<IntList> glyphsByWidth = new Int2ObjectOpenHashMap<>(); |
| 62 | + private final CodepointMap<FontSet.SelectedGlyphs> glyphCache = new CodepointMap<>(FontSet.SelectedGlyphs[]::new, FontSet.SelectedGlyphs[][]::new); |
| 63 | + private final IntFunction<FontSet.SelectedGlyphs> glyphGetter = this::computeGlyphInfo; |
| 64 | + BakedGlyph missingGlyph = INVISIBLE_MISSING_GLYPH; |
| 65 | + private final Supplier<BakedGlyph> missingGlyphGetter = () -> this.missingGlyph; |
| 66 | + private final FontSet.SelectedGlyphs missingSelectedGlyphs = new FontSet.SelectedGlyphs(this.missingGlyphGetter, this.missingGlyphGetter); |
| 67 | + @Nullable |
| 68 | + private EffectGlyph whiteGlyph; |
| 69 | + private final GlyphSource anyGlyphs = new FontSet.Source(false); |
| 70 | + private final GlyphSource nonFishyGlyphs = new FontSet.Source(true); |
| 71 | + |
| 72 | + public FontSet(GlyphStitcher glyphStitcher) { |
| 73 | + this.stitcher = glyphStitcher; |
| 74 | + } |
| 75 | + |
| 76 | + public void reload(List<GlyphProvider.Conditional> list, Set<FontOption> set) { |
| 77 | + this.allProviders = list; |
| 78 | + this.reload(set); |
| 79 | + } |
| 80 | + |
| 81 | + public void reload(Set<FontOption> set) { |
| 82 | + this.activeProviders = List.of(); |
| 83 | + this.resetTextures(); |
| 84 | + this.activeProviders = this.selectProviders(this.allProviders, set); |
| 85 | + } |
| 86 | + |
| 87 | + private void resetTextures() { |
| 88 | + this.stitcher.reset(); |
| 89 | + this.glyphCache.clear(); |
| 90 | + this.glyphsByWidth.clear(); |
| 91 | + this.missingGlyph = (BakedGlyph)Objects.requireNonNull(SpecialGlyphs.MISSING.bake(this.stitcher)); |
| 92 | + this.whiteGlyph = SpecialGlyphs.WHITE.bake(this.stitcher); |
| 93 | + } |
| 94 | + |
| 95 | + private List<GlyphProvider> selectProviders(List<GlyphProvider.Conditional> list, Set<FontOption> set) { |
| 96 | + IntSet intSet = new IntOpenHashSet(); |
| 97 | + List<GlyphProvider> list2 = new ArrayList(); |
| 98 | + |
| 99 | + for (GlyphProvider.Conditional conditional : list) { |
| 100 | + if (conditional.filter().apply(set)) { |
| 101 | + list2.add(conditional.provider()); |
| 102 | + intSet.addAll(conditional.provider().getSupportedGlyphs()); |
| 103 | + } |
| 104 | + } |
| 105 | + |
| 106 | + Set<GlyphProvider> set2 = Sets.<GlyphProvider>newHashSet(); |
| 107 | + intSet.forEach( |
| 108 | + i -> { |
| 109 | + for (GlyphProvider glyphProvider : list2) { |
| 110 | + UnbakedGlyph unbakedGlyph = glyphProvider.getGlyph(i); |
| 111 | + if (unbakedGlyph != null) { |
| 112 | + set2.add(glyphProvider); |
| 113 | + if (unbakedGlyph.info() != SpecialGlyphs.MISSING) { |
| 114 | + this.glyphsByWidth |
| 115 | + .computeIfAbsent(Mth.ceil(unbakedGlyph.info().getAdvance(false)), (Int2ObjectFunction<? extends IntList>)(ix -> new IntArrayList())) |
| 116 | + .add(i); |
| 117 | + } |
| 118 | + break; |
| 119 | + } |
| 120 | + } |
| 121 | + } |
| 122 | + ); |
| 123 | + return list2.stream().filter(set2::contains).toList(); |
| 124 | + } |
| 125 | + |
| 126 | + public void close() { |
| 127 | + this.stitcher.close(); |
| 128 | + } |
| 129 | + |
| 130 | + private static boolean hasFishyAdvance(GlyphInfo glyphInfo) { |
| 131 | + float f = glyphInfo.getAdvance(false); |
| 132 | + if (!(f < 0.0F) && !(f > 32.0F)) { |
| 133 | + float g = glyphInfo.getAdvance(true); |
| 134 | + return g < 0.0F || g > 32.0F; |
| 135 | + } else { |
| 136 | + return true; |
| 137 | + } |
| 138 | + } |
| 139 | + |
| 140 | + private FontSet.SelectedGlyphs computeGlyphInfo(int i) { |
| 141 | + FontSet.DelayedBake delayedBake = null; |
| 142 | + |
| 143 | + for (GlyphProvider glyphProvider : this.activeProviders) { |
| 144 | + UnbakedGlyph unbakedGlyph = glyphProvider.getGlyph(i); |
| 145 | + if (unbakedGlyph != null) { |
| 146 | + if (delayedBake == null) { |
| 147 | + delayedBake = new FontSet.DelayedBake(unbakedGlyph); |
| 148 | + } |
| 149 | + |
| 150 | + if (!hasFishyAdvance(unbakedGlyph.info())) { |
| 151 | + if (delayedBake.unbaked == unbakedGlyph) { |
| 152 | + return new FontSet.SelectedGlyphs(delayedBake, delayedBake); |
| 153 | + } |
| 154 | + |
| 155 | + return new FontSet.SelectedGlyphs(delayedBake, new FontSet.DelayedBake(unbakedGlyph)); |
| 156 | + } |
| 157 | + } |
| 158 | + } |
| 159 | + |
| 160 | + return delayedBake != null ? new FontSet.SelectedGlyphs(delayedBake, this.missingGlyphGetter) : this.missingSelectedGlyphs; |
| 161 | + } |
| 162 | + |
| 163 | + FontSet.SelectedGlyphs getGlyph(int i) { |
| 164 | + return this.glyphCache.computeIfAbsent(i, this.glyphGetter); |
| 165 | + } |
| 166 | + |
| 167 | + public BakedGlyph getRandomGlyph(RandomSource randomSource, int i) { |
| 168 | + IntList intList = this.glyphsByWidth.get(i); |
| 169 | + return intList != null && !intList.isEmpty() |
| 170 | + ? (BakedGlyph)this.getGlyph(intList.getInt(randomSource.nextInt(intList.size()))).nonFishy().get() |
| 171 | + : this.missingGlyph; |
| 172 | + } |
| 173 | + |
| 174 | + public EffectGlyph whiteGlyph() { |
| 175 | + return (EffectGlyph)Objects.requireNonNull(this.whiteGlyph); |
| 176 | + } |
| 177 | + |
| 178 | + public GlyphSource source(boolean bl) { |
| 179 | + return bl ? this.nonFishyGlyphs : this.anyGlyphs; |
| 180 | + } |
| 181 | + |
| 182 | + @Environment(EnvType.CLIENT) |
| 183 | + class DelayedBake implements Supplier<BakedGlyph> { |
| 184 | + final UnbakedGlyph unbaked; |
| 185 | + @Nullable |
| 186 | + private BakedGlyph baked; |
| 187 | + |
| 188 | + DelayedBake(final UnbakedGlyph unbakedGlyph) { |
| 189 | + this.unbaked = unbakedGlyph; |
| 190 | + } |
| 191 | + |
| 192 | + public BakedGlyph get() { |
| 193 | + if (this.baked == null) { |
| 194 | + this.baked = this.unbaked.bake(FontSet.this.wrappedStitcher); |
| 195 | + } |
| 196 | + |
| 197 | + return this.baked; |
| 198 | + } |
| 199 | + } |
| 200 | + |
| 201 | + @Environment(EnvType.CLIENT) |
| 202 | + record SelectedGlyphs(Supplier<BakedGlyph> any, Supplier<BakedGlyph> nonFishy) { |
| 203 | + Supplier<BakedGlyph> select(boolean bl) { |
| 204 | + return bl ? this.nonFishy : this.any; |
| 205 | + } |
| 206 | + } |
| 207 | + |
| 208 | + @Environment(EnvType.CLIENT) |
| 209 | + public class Source implements GlyphSource { |
| 210 | + private final boolean filterFishyGlyphs; |
| 211 | + |
| 212 | + public Source(final boolean bl) { |
| 213 | + this.filterFishyGlyphs = bl; |
| 214 | + } |
| 215 | + |
| 216 | + @Override |
| 217 | + public BakedGlyph getGlyph(int i) { |
| 218 | + return (BakedGlyph)FontSet.this.getGlyph(i).select(this.filterFishyGlyphs).get(); |
| 219 | + } |
| 220 | + |
| 221 | + @Override |
| 222 | + public BakedGlyph getRandomGlyph(RandomSource randomSource, int i) { |
| 223 | + return FontSet.this.getRandomGlyph(randomSource, i); |
| 224 | + } |
| 225 | + } |
| 226 | +} |
0 commit comments