package net.minecraft.client.gui; import com.google.common.collect.Lists; import com.ibm.icu.text.ArabicShaping; import com.ibm.icu.text.ArabicShapingException; import com.ibm.icu.text.Bidi; import com.mojang.blaze3d.font.GlyphInfo; import com.mojang.blaze3d.vertex.VertexConsumer; import java.util.ArrayList; import java.util.List; import java.util.function.Function; import javax.annotation.Nullable; import net.minecraft.client.StringSplitter; import net.minecraft.client.gui.font.FontSet; import net.minecraft.client.gui.font.glyphs.BakedGlyph; import net.minecraft.client.gui.font.glyphs.EmptyGlyph; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.locale.Language; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.FormattedText; import net.minecraft.network.chat.Style; import net.minecraft.network.chat.TextColor; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.ARGB; import net.minecraft.util.FormattedCharSequence; import net.minecraft.util.FormattedCharSink; import net.minecraft.util.Mth; import net.minecraft.util.RandomSource; import net.minecraft.util.StringDecomposer; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import org.joml.Matrix4f; @OnlyIn(Dist.CLIENT) public class Font { private static final float EFFECT_DEPTH = 0.01F; public static final float SHADOW_DEPTH = 0.03F; public static final int NO_SHADOW = 0; public static final int ALPHA_CUTOFF = 8; public final int lineHeight = 9; public final RandomSource random = RandomSource.create(); private final Function fonts; final boolean filterFishyGlyphs; private final StringSplitter splitter; public Font(Function p_243253_, boolean p_243245_) { this.fonts = p_243253_; this.filterFishyGlyphs = p_243245_; this.splitter = new StringSplitter( (p_92722_, p_92723_) -> this.getFontSet(p_92723_.getFont()).getGlyphInfo(p_92722_, this.filterFishyGlyphs).getAdvance(p_92723_.isBold()) ); } FontSet getFontSet(ResourceLocation p_92864_) { return this.fonts.apply(p_92864_); } public String bidirectionalShaping(String p_92802_) { try { Bidi bidi = new Bidi(new ArabicShaping(8).shape(p_92802_), 127); bidi.setReorderingMode(0); return bidi.writeReordered(2); } catch (ArabicShapingException arabicshapingexception) { return p_92802_; } } public int drawInBatch( String p_272751_, float p_272661_, float p_273129_, int p_273272_, boolean p_273209_, Matrix4f p_272940_, MultiBufferSource p_273017_, Font.DisplayMode p_272608_, int p_273365_, int p_272755_ ) { if (this.isBidirectional()) { p_272751_ = this.bidirectionalShaping(p_272751_); } return this.drawInternal(p_272751_, p_272661_, p_273129_, p_273272_, p_273209_, p_272940_, p_273017_, p_272608_, p_273365_, p_272755_, true); } public int drawInBatch( Component p_273032_, float p_273249_, float p_273594_, int p_273714_, boolean p_273050_, Matrix4f p_272974_, MultiBufferSource p_273695_, Font.DisplayMode p_272782_, int p_272603_, int p_273632_ ) { return this.drawInBatch(p_273032_, p_273249_, p_273594_, p_273714_, p_273050_, p_272974_, p_273695_, p_272782_, p_272603_, p_273632_, true); } public int drawInBatch( Component p_363863_, float p_272811_, float p_272610_, int p_273422_, boolean p_273016_, Matrix4f p_273443_, MultiBufferSource p_273387_, Font.DisplayMode p_273551_, int p_272706_, int p_273114_, boolean p_273022_ ) { return this.drawInternal(p_363863_.getVisualOrderText(), p_272811_, p_272610_, p_273422_, p_273016_, p_273443_, p_273387_, p_273551_, p_272706_, p_273114_, p_273022_); } public int drawInBatch( FormattedCharSequence p_273262_, float p_273006_, float p_273254_, int p_273375_, boolean p_273674_, Matrix4f p_273525_, MultiBufferSource p_272624_, Font.DisplayMode p_273418_, int p_273330_, int p_272981_ ) { return this.drawInternal(p_273262_, p_273006_, p_273254_, p_273375_, p_273674_, p_273525_, p_272624_, p_273418_, p_273330_, p_272981_, true); } public void drawInBatch8xOutline( FormattedCharSequence p_168646_, float p_168647_, float p_168648_, int p_168649_, int p_168650_, Matrix4f p_254170_, MultiBufferSource p_168652_, int p_168653_ ) { int i = adjustColor(p_168650_); Font.StringRenderOutput font$stringrenderoutput = new Font.StringRenderOutput( p_168652_, 0.0F, 0.0F, i, false, p_254170_, Font.DisplayMode.NORMAL, p_168653_ ); for (int j = -1; j <= 1; j++) { for (int k = -1; k <= 1; k++) { if (j != 0 || k != 0) { float[] afloat = new float[]{p_168647_}; int l = j; int i1 = k; p_168646_.accept((p_168661_, p_168662_, p_168663_) -> { boolean flag = p_168662_.isBold(); FontSet fontset = this.getFontSet(p_168662_.getFont()); GlyphInfo glyphinfo = fontset.getGlyphInfo(p_168663_, this.filterFishyGlyphs); font$stringrenderoutput.x = afloat[0] + (float)l * glyphinfo.getShadowOffset(); font$stringrenderoutput.y = p_168648_ + (float)i1 * glyphinfo.getShadowOffset(); afloat[0] += glyphinfo.getAdvance(flag); return font$stringrenderoutput.accept(p_168661_, p_168662_.withColor(i), p_168663_); }); } } } font$stringrenderoutput.renderCharacters(); Font.StringRenderOutput font$stringrenderoutput1 = new Font.StringRenderOutput( p_168652_, p_168647_, p_168648_, adjustColor(p_168649_), false, p_254170_, Font.DisplayMode.POLYGON_OFFSET, p_168653_ ); p_168646_.accept(font$stringrenderoutput1); font$stringrenderoutput1.finish(p_168647_); } private static int adjustColor(int p_92720_) { return (p_92720_ & -67108864) == 0 ? ARGB.opaque(p_92720_) : p_92720_; } private int drawInternal( String p_273658_, float p_273086_, float p_272883_, int p_273547_, boolean p_272778_, Matrix4f p_272662_, MultiBufferSource p_273012_, Font.DisplayMode p_273381_, int p_272855_, int p_272745_, boolean p_272785_ ) { p_273547_ = adjustColor(p_273547_); p_273086_ = this.renderText(p_273658_, p_273086_, p_272883_, p_273547_, p_272778_, p_272662_, p_273012_, p_273381_, p_272855_, p_272745_, p_272785_); return (int)p_273086_ + (p_272778_ ? 1 : 0); } private int drawInternal( FormattedCharSequence p_273025_, float p_273121_, float p_272717_, int p_273653_, boolean p_273531_, Matrix4f p_273265_, MultiBufferSource p_273560_, Font.DisplayMode p_273342_, int p_273373_, int p_273266_, boolean p_362833_ ) { p_273653_ = adjustColor(p_273653_); p_273121_ = this.renderText(p_273025_, p_273121_, p_272717_, p_273653_, p_273531_, p_273265_, p_273560_, p_273342_, p_273373_, p_273266_, p_362833_); return (int)p_273121_ + (p_273531_ ? 1 : 0); } private float renderText( String p_273765_, float p_273532_, float p_272783_, int p_273217_, boolean p_273583_, Matrix4f p_272734_, MultiBufferSource p_272595_, Font.DisplayMode p_273610_, int p_273727_, int p_273199_, boolean p_369545_ ) { Font.StringRenderOutput font$stringrenderoutput = new Font.StringRenderOutput( p_272595_, p_273532_, p_272783_, p_273217_, p_273727_, p_273583_, p_272734_, p_273610_, p_273199_, p_369545_ ); StringDecomposer.iterateFormatted(p_273765_, Style.EMPTY, font$stringrenderoutput); return font$stringrenderoutput.finish(p_273532_); } private float renderText( FormattedCharSequence p_273322_, float p_272632_, float p_273541_, int p_273200_, boolean p_273312_, Matrix4f p_273276_, MultiBufferSource p_273392_, Font.DisplayMode p_272625_, int p_273774_, int p_273371_, boolean p_365371_ ) { Font.StringRenderOutput font$stringrenderoutput = new Font.StringRenderOutput( p_273392_, p_272632_, p_273541_, p_273200_, p_273774_, p_273312_, p_273276_, p_272625_, p_273371_, p_365371_ ); p_273322_.accept(font$stringrenderoutput); return font$stringrenderoutput.finish(p_272632_); } public int width(String p_92896_) { return Mth.ceil(this.splitter.stringWidth(p_92896_)); } public int width(FormattedText p_92853_) { return Mth.ceil(this.splitter.stringWidth(p_92853_)); } public int width(FormattedCharSequence p_92725_) { return Mth.ceil(this.splitter.stringWidth(p_92725_)); } public String plainSubstrByWidth(String p_92838_, int p_92839_, boolean p_92840_) { return p_92840_ ? this.splitter.plainTailByWidth(p_92838_, p_92839_, Style.EMPTY) : this.splitter.plainHeadByWidth(p_92838_, p_92839_, Style.EMPTY); } public String plainSubstrByWidth(String p_92835_, int p_92836_) { return this.splitter.plainHeadByWidth(p_92835_, p_92836_, Style.EMPTY); } public FormattedText substrByWidth(FormattedText p_92855_, int p_92856_) { return this.splitter.headByWidth(p_92855_, p_92856_, Style.EMPTY); } public int wordWrapHeight(String p_92921_, int p_92922_) { return 9 * this.splitter.splitLines(p_92921_, p_92922_, Style.EMPTY).size(); } public int wordWrapHeight(FormattedText p_239134_, int p_239135_) { return 9 * this.splitter.splitLines(p_239134_, p_239135_, Style.EMPTY).size(); } public List split(FormattedText p_92924_, int p_92925_) { return Language.getInstance().getVisualOrder(this.splitter.splitLines(p_92924_, p_92925_, Style.EMPTY)); } public boolean isBidirectional() { return Language.getInstance().isDefaultRightToLeft(); } public StringSplitter getSplitter() { return this.splitter; } @OnlyIn(Dist.CLIENT) public static enum DisplayMode { NORMAL, SEE_THROUGH, POLYGON_OFFSET; } @OnlyIn(Dist.CLIENT) class StringRenderOutput implements FormattedCharSink { final MultiBufferSource bufferSource; private final boolean drawShadow; private final int color; private final int backgroundColor; private final Matrix4f pose; private final Font.DisplayMode mode; private final int packedLightCoords; private final boolean inverseDepth; float x; float y; private final List glyphInstances; @Nullable private List effects; private void addEffect(BakedGlyph.Effect p_92965_) { if (this.effects == null) { this.effects = Lists.newArrayList(); } this.effects.add(p_92965_); } public StringRenderOutput( final MultiBufferSource p_181365_, final float p_181366_, final float p_181367_, final int p_181368_, final boolean p_181369_, final Matrix4f p_254510_, final Font.DisplayMode p_181371_, final int p_181372_ ) { this(p_181365_, p_181366_, p_181367_, p_181368_, 0, p_181369_, p_254510_, p_181371_, p_181372_, true); } public StringRenderOutput( final MultiBufferSource p_369661_, final float p_364376_, final float p_367232_, final int p_363543_, final int p_366865_, final boolean p_369620_, final Matrix4f p_365215_, final Font.DisplayMode p_366135_, final int p_361543_, final boolean p_363799_ ) { this.glyphInstances = new ArrayList<>(); this.bufferSource = p_369661_; this.x = p_364376_; this.y = p_367232_; this.drawShadow = p_369620_; this.color = p_363543_; this.backgroundColor = p_366865_; this.pose = p_365215_; this.mode = p_366135_; this.packedLightCoords = p_361543_; this.inverseDepth = p_363799_; } @Override public boolean accept(int p_92967_, Style p_92968_, int p_92969_) { FontSet fontset = Font.this.getFontSet(p_92968_.getFont()); GlyphInfo glyphinfo = fontset.getGlyphInfo(p_92969_, Font.this.filterFishyGlyphs); BakedGlyph bakedglyph = p_92968_.isObfuscated() && p_92969_ != 32 ? fontset.getRandomGlyph(glyphinfo) : fontset.getGlyph(p_92969_); boolean flag = p_92968_.isBold(); TextColor textcolor = p_92968_.getColor(); int i = this.getTextColor(textcolor); int j = this.getShadowColor(p_92968_, i); float f = glyphinfo.getAdvance(flag); float f1 = p_92967_ == 0 ? this.x - 1.0F : this.x; float f2 = glyphinfo.getShadowOffset(); if (!(bakedglyph instanceof EmptyGlyph)) { float f3 = flag ? glyphinfo.getBoldOffset() : 0.0F; this.glyphInstances.add(new BakedGlyph.GlyphInstance(this.x, this.y, i, j, bakedglyph, p_92968_, f3, f2)); } if (p_92968_.isStrikethrough()) { this.addEffect(new BakedGlyph.Effect(f1, this.y + 4.5F, this.x + f, this.y + 4.5F - 1.0F, this.getOverTextEffectDepth(), i, j, f2)); } if (p_92968_.isUnderlined()) { this.addEffect(new BakedGlyph.Effect(f1, this.y + 9.0F, this.x + f, this.y + 9.0F - 1.0F, this.getOverTextEffectDepth(), i, j, f2)); } this.x += f; return true; } float finish(float p_92963_) { BakedGlyph bakedglyph = null; if (this.backgroundColor != 0) { BakedGlyph.Effect bakedglyph$effect = new BakedGlyph.Effect( p_92963_ - 1.0F, this.y + 9.0F, this.x, this.y - 1.0F, this.getUnderTextEffectDepth(), this.backgroundColor ); bakedglyph = Font.this.getFontSet(Style.DEFAULT_FONT).whiteGlyph(); VertexConsumer vertexconsumer = this.bufferSource.getBuffer(bakedglyph.renderType(this.mode)); bakedglyph.renderEffect(bakedglyph$effect, this.pose, vertexconsumer, this.packedLightCoords); } this.renderCharacters(); if (this.effects != null) { if (bakedglyph == null) { bakedglyph = Font.this.getFontSet(Style.DEFAULT_FONT).whiteGlyph(); } VertexConsumer vertexconsumer1 = this.bufferSource.getBuffer(bakedglyph.renderType(this.mode)); for (BakedGlyph.Effect bakedglyph$effect1 : this.effects) { bakedglyph.renderEffect(bakedglyph$effect1, this.pose, vertexconsumer1, this.packedLightCoords); } } return this.x; } private int getTextColor(@Nullable TextColor p_378404_) { if (p_378404_ != null) { int i = ARGB.alpha(this.color); int j = p_378404_.getValue(); return ARGB.color(i, j); } else { return this.color; } } private int getShadowColor(Style p_378453_, int p_375981_) { Integer integer = p_378453_.getShadowColor(); if (integer != null) { float f = ARGB.alphaFloat(p_375981_); float f1 = ARGB.alphaFloat(integer); return f != 1.0F ? ARGB.color(ARGB.as8BitChannel(f * f1), integer) : integer; } else { return this.drawShadow ? ARGB.scaleRGB(p_375981_, 0.25F) : 0; } } void renderCharacters() { for (BakedGlyph.GlyphInstance bakedglyph$glyphinstance : this.glyphInstances) { BakedGlyph bakedglyph = bakedglyph$glyphinstance.glyph(); VertexConsumer vertexconsumer = this.bufferSource.getBuffer(bakedglyph.renderType(this.mode)); bakedglyph.renderChar(bakedglyph$glyphinstance, this.pose, vertexconsumer, this.packedLightCoords); } } private float getOverTextEffectDepth() { return this.inverseDepth ? 0.01F : -0.01F; } private float getUnderTextEffectDepth() { return this.inverseDepth ? -0.01F : 0.01F; } } }