399 lines
17 KiB
Java
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);
|
|
}
|
|
}
|
|
}
|
|
|