2025-11-24 22:52:51 +03:00

399 lines
17 KiB
Java

/*
* 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<FormattedCharSequence> split(FormattedText input, int maxWidth) {
return Language.getInstance().getVisualOrder(this.splitter.splitLines(input, maxWidth, Style.EMPTY));
}
public List<FormattedText> 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<TextRenderable.Styled> glyphs = new ArrayList<TextRenderable.Styled>();
private @Nullable List<TextRenderable> effects;
private @Nullable List<EmptyArea> 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<TextRenderable>();
}
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<EmptyArea>();
}
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);
}
}
}