/* * Decompiled with CFR 0.152. * * Could not load the following classes: * com.ibm.icu.text.ArabicShaping * com.ibm.icu.text.ArabicShapingException * com.ibm.icu.text.Bidi * org.joml.Matrix4f * org.jspecify.annotations.Nullable */ package net.minecraft.client.gui; 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 net.minecraft.client.StringSplitter; import net.minecraft.client.gui.GlyphSource; import net.minecraft.client.gui.font.EmptyArea; import net.minecraft.client.gui.font.TextRenderable; import net.minecraft.client.gui.font.glyphs.BakedGlyph; import net.minecraft.client.gui.font.glyphs.EffectGlyph; import net.minecraft.client.gui.navigation.ScreenRectangle; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.locale.Language; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.FontDescription; import net.minecraft.network.chat.FormattedText; import net.minecraft.network.chat.Style; import net.minecraft.network.chat.TextColor; 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 org.joml.Matrix4f; import org.jspecify.annotations.Nullable; public class Font { private static final float EFFECT_DEPTH = 0.01f; private static final float OVER_EFFECT_DEPTH = 0.01f; private static final float UNDER_EFFECT_DEPTH = -0.01f; public static final float SHADOW_DEPTH = 0.03f; public final int lineHeight = 9; private final RandomSource random = RandomSource.create(); private final Provider provider; private final StringSplitter splitter; public Font(Provider provider) { this.provider = provider; this.splitter = new StringSplitter((codepoint, style) -> this.getGlyphSource(style.getFont()).getGlyph(codepoint).info().getAdvance(style.isBold())); } private GlyphSource getGlyphSource(FontDescription fontLocation) { return this.provider.glyphs(fontLocation); } public String bidirectionalShaping(String text) { try { Bidi bidi = new Bidi(new ArabicShaping(8).shape(text), 127); bidi.setReorderingMode(0); return bidi.writeReordered(2); } catch (ArabicShapingException arabicShapingException) { return text; } } public void drawInBatch(String str, float x, float y, int color, boolean dropShadow, Matrix4f pose, MultiBufferSource bufferSource, DisplayMode displayMode, int backgroundColor, int packedLightCoords) { PreparedText preparedText = this.prepareText(str, x, y, color, dropShadow, backgroundColor); preparedText.visit(GlyphVisitor.forMultiBufferSource(bufferSource, pose, displayMode, packedLightCoords)); } public void drawInBatch(Component str, float x, float y, int color, boolean dropShadow, Matrix4f pose, MultiBufferSource bufferSource, DisplayMode displayMode, int backgroundColor, int packedLightCoords) { PreparedText preparedText = this.prepareText(str.getVisualOrderText(), x, y, color, dropShadow, false, backgroundColor); preparedText.visit(GlyphVisitor.forMultiBufferSource(bufferSource, pose, displayMode, packedLightCoords)); } public void drawInBatch(FormattedCharSequence str, float x, float y, int color, boolean dropShadow, Matrix4f pose, MultiBufferSource bufferSource, DisplayMode displayMode, int backgroundColor, int packedLightCoords) { PreparedText preparedText = this.prepareText(str, x, y, color, dropShadow, false, backgroundColor); preparedText.visit(GlyphVisitor.forMultiBufferSource(bufferSource, pose, displayMode, packedLightCoords)); } public void drawInBatch8xOutline(FormattedCharSequence str, float x, float y, int color, int outlineColor, Matrix4f pose, MultiBufferSource bufferSource, int packedLightCoords) { PreparedTextBuilder outlineOutput = new PreparedTextBuilder(0.0f, 0.0f, outlineColor, false, false); for (int xo = -1; xo <= 1; ++xo) { for (int yo = -1; yo <= 1; ++yo) { if (xo == 0 && yo == 0) continue; float[] startX = new float[]{x}; int finalXo = xo; int finalYo = yo; str.accept((position, style, codepoint) -> { boolean bold = style.isBold(); BakedGlyph glyph = this.getGlyph(codepoint, style); outlineOutput.x = startX[0] + (float)finalXo * glyph.info().getShadowOffset(); outlineOutput.y = y + (float)finalYo * glyph.info().getShadowOffset(); startX[0] = startX[0] + glyph.info().getAdvance(bold); return outlineOutput.accept(position, style.withColor(outlineColor), glyph); }); } } GlyphVisitor outlineGlyphVisitor = GlyphVisitor.forMultiBufferSource(bufferSource, pose, DisplayMode.NORMAL, packedLightCoords); for (TextRenderable.Styled glyphInstance : outlineOutput.glyphs) { outlineGlyphVisitor.acceptGlyph(glyphInstance); } PreparedTextBuilder primaryOutput = new PreparedTextBuilder(x, y, color, false, true); str.accept(primaryOutput); primaryOutput.visit(GlyphVisitor.forMultiBufferSource(bufferSource, pose, DisplayMode.POLYGON_OFFSET, packedLightCoords)); } private BakedGlyph getGlyph(int codepoint, Style style) { GlyphSource glyphSource = this.getGlyphSource(style.getFont()); BakedGlyph glyph = glyphSource.getGlyph(codepoint); if (style.isObfuscated() && codepoint != 32) { int targetWidth = Mth.ceil(glyph.info().getAdvance(false)); glyph = glyphSource.getRandomGlyph(this.random, targetWidth); } return glyph; } public PreparedText prepareText(String text, float x, float y, int originalColor, boolean drawShadow, int backgroundColor) { if (this.isBidirectional()) { text = this.bidirectionalShaping(text); } PreparedTextBuilder output = new PreparedTextBuilder(x, y, originalColor, backgroundColor, drawShadow, false); StringDecomposer.iterateFormatted(text, Style.EMPTY, (FormattedCharSink)output); return output; } public PreparedText prepareText(FormattedCharSequence text, float x, float y, int originalColor, boolean drawShadow, boolean includeEmpty, int backgroundColor) { PreparedTextBuilder builder = new PreparedTextBuilder(x, y, originalColor, backgroundColor, drawShadow, includeEmpty); text.accept(builder); return builder; } public int width(String str) { return Mth.ceil(this.splitter.stringWidth(str)); } public int width(FormattedText text) { return Mth.ceil(this.splitter.stringWidth(text)); } public int width(FormattedCharSequence text) { return Mth.ceil(this.splitter.stringWidth(text)); } public String plainSubstrByWidth(String str, int width, boolean reverse) { return reverse ? this.splitter.plainTailByWidth(str, width, Style.EMPTY) : this.splitter.plainHeadByWidth(str, width, Style.EMPTY); } public String plainSubstrByWidth(String str, int width) { return this.splitter.plainHeadByWidth(str, width, Style.EMPTY); } public FormattedText substrByWidth(FormattedText text, int width) { return this.splitter.headByWidth(text, width, Style.EMPTY); } public int wordWrapHeight(FormattedText input, int textWidth) { return 9 * this.splitter.splitLines(input, textWidth, Style.EMPTY).size(); } public List split(FormattedText input, int maxWidth) { return Language.getInstance().getVisualOrder(this.splitter.splitLines(input, maxWidth, Style.EMPTY)); } public List splitIgnoringLanguage(FormattedText input, int maxWidth) { return this.splitter.splitLines(input, maxWidth, Style.EMPTY); } public boolean isBidirectional() { return Language.getInstance().isDefaultRightToLeft(); } public StringSplitter getSplitter() { return this.splitter; } public static interface Provider { public GlyphSource glyphs(FontDescription var1); public EffectGlyph effect(); } public static interface PreparedText { public void visit(GlyphVisitor var1); public @Nullable ScreenRectangle bounds(); } public static interface GlyphVisitor { public static GlyphVisitor forMultiBufferSource(final MultiBufferSource bufferSource, final Matrix4f pose, final DisplayMode displayMode, final int lightCoords) { return new GlyphVisitor(){ @Override public void acceptGlyph(TextRenderable.Styled glyph) { this.render(glyph); } @Override public void acceptEffect(TextRenderable effect) { this.render(effect); } private void render(TextRenderable glyph) { VertexConsumer buffer = bufferSource.getBuffer(glyph.renderType(displayMode)); glyph.render(pose, buffer, lightCoords, false); } }; } default public void acceptGlyph(TextRenderable.Styled glyph) { } default public void acceptEffect(TextRenderable effect) { } default public void acceptEmptyArea(EmptyArea empty) { } } public static enum DisplayMode { NORMAL, SEE_THROUGH, POLYGON_OFFSET; } private class PreparedTextBuilder implements PreparedText, FormattedCharSink { private final boolean drawShadow; private final int color; private final int backgroundColor; private final boolean includeEmpty; private float x; private float y; private float left = Float.MAX_VALUE; private float top = Float.MAX_VALUE; private float right = -3.4028235E38f; private float bottom = -3.4028235E38f; private float backgroundLeft = Float.MAX_VALUE; private float backgroundTop = Float.MAX_VALUE; private float backgroundRight = -3.4028235E38f; private float backgroundBottom = -3.4028235E38f; private final List glyphs = new ArrayList(); private @Nullable List effects; private @Nullable List emptyAreas; public PreparedTextBuilder(float x, float y, int color, boolean drawShadow, boolean includeEmpty) { this(x, y, color, 0, drawShadow, includeEmpty); } public PreparedTextBuilder(float x, float y, int color, int backgroundColor, boolean drawShadow, boolean includeEmpty) { this.x = x; this.y = y; this.drawShadow = drawShadow; this.color = color; this.backgroundColor = backgroundColor; this.includeEmpty = includeEmpty; this.markBackground(x, y, 0.0f); } private void markSize(float left, float top, float right, float bottom) { this.left = Math.min(this.left, left); this.top = Math.min(this.top, top); this.right = Math.max(this.right, right); this.bottom = Math.max(this.bottom, bottom); } private void markBackground(float x, float y, float advance) { if (ARGB.alpha(this.backgroundColor) == 0) { return; } this.backgroundLeft = Math.min(this.backgroundLeft, x - 1.0f); this.backgroundTop = Math.min(this.backgroundTop, y - 1.0f); this.backgroundRight = Math.max(this.backgroundRight, x + advance); this.backgroundBottom = Math.max(this.backgroundBottom, y + 9.0f); this.markSize(this.backgroundLeft, this.backgroundTop, this.backgroundRight, this.backgroundBottom); } private void addGlyph(TextRenderable.Styled instance) { this.glyphs.add(instance); this.markSize(instance.left(), instance.top(), instance.right(), instance.bottom()); } private void addEffect(TextRenderable effect) { if (this.effects == null) { this.effects = new ArrayList(); } this.effects.add(effect); this.markSize(effect.left(), effect.top(), effect.right(), effect.bottom()); } private void addEmptyGlyph(EmptyArea empty) { if (this.emptyAreas == null) { this.emptyAreas = new ArrayList(); } this.emptyAreas.add(empty); } @Override public boolean accept(int position, Style style, int c) { BakedGlyph glyph = Font.this.getGlyph(c, style); return this.accept(position, style, glyph); } public boolean accept(int position, Style style, BakedGlyph glyph) { float shadowOffset; GlyphInfo glyphInfo = glyph.info(); boolean bold = style.isBold(); TextColor styleColor = style.getColor(); int textColor = this.getTextColor(styleColor); int shadowColor = this.getShadowColor(style, textColor); float advance = glyphInfo.getAdvance(bold); float effectX0 = position == 0 ? this.x - 1.0f : this.x; float boldOffset = bold ? glyphInfo.getBoldOffset() : 0.0f; TextRenderable.Styled instance = glyph.createGlyph(this.x, this.y, textColor, shadowColor, style, boldOffset, shadowOffset = glyphInfo.getShadowOffset()); if (instance != null) { this.addGlyph(instance); } else if (this.includeEmpty) { this.addEmptyGlyph(new EmptyArea(this.x, this.y, advance, 7.0f, 9.0f, style)); } this.markBackground(this.x, this.y, advance); if (style.isStrikethrough()) { this.addEffect(Font.this.provider.effect().createEffect(effectX0, this.y + 4.5f - 1.0f, this.x + advance, this.y + 4.5f, 0.01f, textColor, shadowColor, shadowOffset)); } if (style.isUnderlined()) { this.addEffect(Font.this.provider.effect().createEffect(effectX0, this.y + 9.0f - 1.0f, this.x + advance, this.y + 9.0f, 0.01f, textColor, shadowColor, shadowOffset)); } this.x += advance; return true; } @Override public void visit(GlyphVisitor visitor) { if (ARGB.alpha(this.backgroundColor) != 0) { visitor.acceptEffect(Font.this.provider.effect().createEffect(this.backgroundLeft, this.backgroundTop, this.backgroundRight, this.backgroundBottom, -0.01f, this.backgroundColor, 0, 0.0f)); } for (TextRenderable.Styled glyph : this.glyphs) { visitor.acceptGlyph(glyph); } if (this.effects != null) { for (TextRenderable effect : this.effects) { visitor.acceptEffect(effect); } } if (this.emptyAreas != null) { for (EmptyArea emptyArea : this.emptyAreas) { visitor.acceptEmptyArea(emptyArea); } } } private int getTextColor(@Nullable TextColor textColor) { if (textColor != null) { int alpha = ARGB.alpha(this.color); int rgb = textColor.getValue(); return ARGB.color(alpha, rgb); } return this.color; } private int getShadowColor(Style style, int textColor) { Integer shadow = style.getShadowColor(); if (shadow != null) { float textAlpha = ARGB.alphaFloat(textColor); float shadowAlpha = ARGB.alphaFloat(shadow); if (textAlpha != 1.0f) { return ARGB.color(ARGB.as8BitChannel(textAlpha * shadowAlpha), (int)shadow); } return shadow; } if (this.drawShadow) { return ARGB.scaleRGB(textColor, 0.25f); } return 0; } @Override public @Nullable ScreenRectangle bounds() { if (this.left >= this.right || this.top >= this.bottom) { return null; } int left = Mth.floor(this.left); int top = Mth.floor(this.top); int right = Mth.ceil(this.right); int bottom = Mth.ceil(this.bottom); return new ScreenRectangle(left, top, right - left, bottom - top); } } }