minecraft-src/net/minecraft/client/gui/Font.java
2025-07-04 01:41:11 +03:00

445 lines
14 KiB
Java

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.List;
import java.util.function.Function;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
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.FormattedCharSequence;
import net.minecraft.util.FormattedCharSink;
import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
import net.minecraft.util.StringDecomposer;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f;
import org.joml.Vector3f;
@Environment(EnvType.CLIENT)
public class Font {
private static final float EFFECT_DEPTH = 0.01F;
private static final Vector3f SHADOW_OFFSET = new Vector3f(0.0F, 0.0F, 0.03F);
public static final int ALPHA_CUTOFF = 8;
public final int lineHeight = 9;
public final RandomSource random = RandomSource.create();
private final Function<ResourceLocation, FontSet> fonts;
final boolean filterFishyGlyphs;
private final StringSplitter splitter;
public Font(Function<ResourceLocation, FontSet> fonts, boolean filterFishyGlyphs) {
this.fonts = fonts;
this.filterFishyGlyphs = filterFishyGlyphs;
this.splitter = new StringSplitter((i, style) -> this.getFontSet(style.getFont()).getGlyphInfo(i, this.filterFishyGlyphs).getAdvance(style.isBold()));
}
FontSet getFontSet(ResourceLocation fontLocation) {
return (FontSet)this.fonts.apply(fontLocation);
}
/**
* Apply Unicode Bidirectional Algorithm to string and return a new possibly reordered string for visual rendering.
*/
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 var3) {
return text;
}
}
public int drawInBatch(
String text,
float x,
float y,
int color,
boolean dropShadow,
Matrix4f matrix,
MultiBufferSource buffer,
Font.DisplayMode displayMode,
int backgroundColor,
int packedLightCoords
) {
return this.drawInBatch(text, x, y, color, dropShadow, matrix, buffer, displayMode, backgroundColor, packedLightCoords, this.isBidirectional());
}
public int drawInBatch(
String text,
float x,
float y,
int color,
boolean dropShadow,
Matrix4f matrix,
MultiBufferSource buffer,
Font.DisplayMode displayMode,
int backgroundColor,
int packedLightCoords,
boolean bidirectional
) {
return this.drawInternal(text, x, y, color, dropShadow, matrix, buffer, displayMode, backgroundColor, packedLightCoords, bidirectional);
}
public int drawInBatch(
Component text,
float x,
float y,
int color,
boolean dropShadow,
Matrix4f matrix,
MultiBufferSource buffer,
Font.DisplayMode displayMode,
int backgroundColor,
int packedLightCoords
) {
return this.drawInBatch(text.getVisualOrderText(), x, y, color, dropShadow, matrix, buffer, displayMode, backgroundColor, packedLightCoords);
}
public int drawInBatch(
FormattedCharSequence text,
float x,
float y,
int color,
boolean dropShadow,
Matrix4f matrix,
MultiBufferSource buffer,
Font.DisplayMode displayMode,
int backgroundColor,
int packedLightCoords
) {
return this.drawInternal(text, x, y, color, dropShadow, matrix, buffer, displayMode, backgroundColor, packedLightCoords);
}
public void drawInBatch8xOutline(
FormattedCharSequence text, float x, float y, int color, int backgroundColor, Matrix4f matrix, MultiBufferSource bufferSource, int packedLightCoords
) {
int i = adjustColor(backgroundColor);
Font.StringRenderOutput stringRenderOutput = new Font.StringRenderOutput(
bufferSource, 0.0F, 0.0F, i, false, matrix, Font.DisplayMode.NORMAL, packedLightCoords
);
for (int j = -1; j <= 1; j++) {
for (int k = -1; k <= 1; k++) {
if (j != 0 || k != 0) {
float[] fs = new float[]{x};
int l = j;
int m = k;
text.accept((lx, style, mx) -> {
boolean bl = style.isBold();
FontSet fontSet = this.getFontSet(style.getFont());
GlyphInfo glyphInfo = fontSet.getGlyphInfo(mx, this.filterFishyGlyphs);
stringRenderOutput.x = fs[0] + l * glyphInfo.getShadowOffset();
stringRenderOutput.y = y + m * glyphInfo.getShadowOffset();
fs[0] += glyphInfo.getAdvance(bl);
return stringRenderOutput.accept(lx, style.withColor(i), mx);
});
}
}
}
Font.StringRenderOutput stringRenderOutput2 = new Font.StringRenderOutput(
bufferSource, x, y, adjustColor(color), false, matrix, Font.DisplayMode.POLYGON_OFFSET, packedLightCoords
);
text.accept(stringRenderOutput2);
stringRenderOutput2.finish(0, x);
}
private static int adjustColor(int color) {
return (color & -67108864) == 0 ? color | 0xFF000000 : color;
}
private int drawInternal(
String text,
float x,
float y,
int color,
boolean dropShadow,
Matrix4f matrix,
MultiBufferSource buffer,
Font.DisplayMode displayMode,
int backgroundColor,
int packedLightCoords,
boolean bidirectional
) {
if (bidirectional) {
text = this.bidirectionalShaping(text);
}
color = adjustColor(color);
Matrix4f matrix4f = new Matrix4f(matrix);
if (dropShadow) {
this.renderText(text, x, y, color, true, matrix, buffer, displayMode, backgroundColor, packedLightCoords);
matrix4f.translate(SHADOW_OFFSET);
}
x = this.renderText(text, x, y, color, false, matrix4f, buffer, displayMode, backgroundColor, packedLightCoords);
return (int)x + (dropShadow ? 1 : 0);
}
private int drawInternal(
FormattedCharSequence text,
float x,
float y,
int color,
boolean dropShadow,
Matrix4f matrix,
MultiBufferSource buffer,
Font.DisplayMode displayMode,
int backgroundColor,
int packedLightCoords
) {
color = adjustColor(color);
Matrix4f matrix4f = new Matrix4f(matrix);
if (dropShadow) {
this.renderText(text, x, y, color, true, matrix, buffer, displayMode, backgroundColor, packedLightCoords);
matrix4f.translate(SHADOW_OFFSET);
}
x = this.renderText(text, x, y, color, false, matrix4f, buffer, displayMode, backgroundColor, packedLightCoords);
return (int)x + (dropShadow ? 1 : 0);
}
private float renderText(
String text,
float x,
float y,
int color,
boolean dropShadow,
Matrix4f matrix,
MultiBufferSource buffer,
Font.DisplayMode displayMode,
int backgroundColor,
int packedLightCoords
) {
Font.StringRenderOutput stringRenderOutput = new Font.StringRenderOutput(buffer, x, y, color, dropShadow, matrix, displayMode, packedLightCoords);
StringDecomposer.iterateFormatted(text, Style.EMPTY, stringRenderOutput);
return stringRenderOutput.finish(backgroundColor, x);
}
private float renderText(
FormattedCharSequence text,
float x,
float y,
int color,
boolean dropShadow,
Matrix4f matrix,
MultiBufferSource buffer,
Font.DisplayMode displayMode,
int backgroundColor,
int packedLightCoords
) {
Font.StringRenderOutput stringRenderOutput = new Font.StringRenderOutput(buffer, x, y, color, dropShadow, matrix, displayMode, packedLightCoords);
text.accept(stringRenderOutput);
return stringRenderOutput.finish(backgroundColor, x);
}
void renderChar(
BakedGlyph glyph,
boolean bold,
boolean italic,
float boldOffset,
float x,
float y,
Matrix4f matrix,
VertexConsumer buffer,
float red,
float green,
float blue,
float alpha,
int packedLight
) {
glyph.render(italic, x, y, matrix, buffer, red, green, blue, alpha, packedLight);
if (bold) {
glyph.render(italic, x + boldOffset, y, matrix, buffer, red, green, blue, alpha, packedLight);
}
}
/**
* Returns the width of this string. Equivalent of FontMetrics.stringWidth(String s).
*/
public int width(String text) {
return Mth.ceil(this.splitter.stringWidth(text));
}
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 text, int maxWidth, boolean tail) {
return tail ? this.splitter.plainTailByWidth(text, maxWidth, Style.EMPTY) : this.splitter.plainHeadByWidth(text, maxWidth, Style.EMPTY);
}
public String plainSubstrByWidth(String text, int maxWidth) {
return this.splitter.plainHeadByWidth(text, maxWidth, Style.EMPTY);
}
public FormattedText substrByWidth(FormattedText text, int maxWidth) {
return this.splitter.headByWidth(text, maxWidth, Style.EMPTY);
}
/**
* Returns the height (in pixels) of the given string if it is wordwrapped to the given max width.
*/
public int wordWrapHeight(String text, int maxWidth) {
return 9 * this.splitter.splitLines(text, maxWidth, Style.EMPTY).size();
}
public int wordWrapHeight(FormattedText text, int maxWidth) {
return 9 * this.splitter.splitLines(text, maxWidth, Style.EMPTY).size();
}
public List<FormattedCharSequence> split(FormattedText text, int maxWidth) {
return Language.getInstance().getVisualOrder(this.splitter.splitLines(text, maxWidth, Style.EMPTY));
}
/**
* Get bidiFlag that controls if the Unicode Bidirectional Algorithm should be run before rendering any string
*/
public boolean isBidirectional() {
return Language.getInstance().isDefaultRightToLeft();
}
public StringSplitter getSplitter() {
return this.splitter;
}
@Environment(EnvType.CLIENT)
public static enum DisplayMode {
NORMAL,
SEE_THROUGH,
POLYGON_OFFSET;
}
@Environment(EnvType.CLIENT)
class StringRenderOutput implements FormattedCharSink {
final MultiBufferSource bufferSource;
private final boolean dropShadow;
private final float dimFactor;
private final float r;
private final float g;
private final float b;
private final float a;
private final Matrix4f pose;
private final Font.DisplayMode mode;
private final int packedLightCoords;
float x;
float y;
@Nullable
private List<BakedGlyph.Effect> effects;
private void addEffect(BakedGlyph.Effect effect) {
if (this.effects == null) {
this.effects = Lists.<BakedGlyph.Effect>newArrayList();
}
this.effects.add(effect);
}
public StringRenderOutput(
final MultiBufferSource bufferSource,
final float x,
final float y,
final int color,
final boolean dropShadow,
final Matrix4f pose,
final Font.DisplayMode mode,
final int packedLightCoords
) {
this.bufferSource = bufferSource;
this.x = x;
this.y = y;
this.dropShadow = dropShadow;
this.dimFactor = dropShadow ? 0.25F : 1.0F;
this.r = (color >> 16 & 0xFF) / 255.0F * this.dimFactor;
this.g = (color >> 8 & 0xFF) / 255.0F * this.dimFactor;
this.b = (color & 0xFF) / 255.0F * this.dimFactor;
this.a = (color >> 24 & 0xFF) / 255.0F;
this.pose = pose;
this.mode = mode;
this.packedLightCoords = packedLightCoords;
}
@Override
public boolean accept(int i, Style style, int j) {
FontSet fontSet = Font.this.getFontSet(style.getFont());
GlyphInfo glyphInfo = fontSet.getGlyphInfo(j, Font.this.filterFishyGlyphs);
BakedGlyph bakedGlyph = style.isObfuscated() && j != 32 ? fontSet.getRandomGlyph(glyphInfo) : fontSet.getGlyph(j);
boolean bl = style.isBold();
float f = this.a;
TextColor textColor = style.getColor();
float g;
float h;
float l;
if (textColor != null) {
int k = textColor.getValue();
g = (k >> 16 & 0xFF) / 255.0F * this.dimFactor;
h = (k >> 8 & 0xFF) / 255.0F * this.dimFactor;
l = (k & 0xFF) / 255.0F * this.dimFactor;
} else {
g = this.r;
h = this.g;
l = this.b;
}
if (!(bakedGlyph instanceof EmptyGlyph)) {
float m = bl ? glyphInfo.getBoldOffset() : 0.0F;
float n = this.dropShadow ? glyphInfo.getShadowOffset() : 0.0F;
VertexConsumer vertexConsumer = this.bufferSource.getBuffer(bakedGlyph.renderType(this.mode));
Font.this.renderChar(bakedGlyph, bl, style.isItalic(), m, this.x + n, this.y + n, this.pose, vertexConsumer, g, h, l, f, this.packedLightCoords);
}
float m = glyphInfo.getAdvance(bl);
float n = this.dropShadow ? 1.0F : 0.0F;
if (style.isStrikethrough()) {
this.addEffect(new BakedGlyph.Effect(this.x + n - 1.0F, this.y + n + 4.5F, this.x + n + m, this.y + n + 4.5F - 1.0F, 0.01F, g, h, l, f));
}
if (style.isUnderlined()) {
this.addEffect(new BakedGlyph.Effect(this.x + n - 1.0F, this.y + n + 9.0F, this.x + n + m, this.y + n + 9.0F - 1.0F, 0.01F, g, h, l, f));
}
this.x += m;
return true;
}
public float finish(int backgroundColor, float x) {
if (backgroundColor != 0) {
float f = (backgroundColor >> 24 & 0xFF) / 255.0F;
float g = (backgroundColor >> 16 & 0xFF) / 255.0F;
float h = (backgroundColor >> 8 & 0xFF) / 255.0F;
float i = (backgroundColor & 0xFF) / 255.0F;
this.addEffect(new BakedGlyph.Effect(x - 1.0F, this.y + 9.0F, this.x + 1.0F, this.y - 1.0F, 0.01F, g, h, i, f));
}
if (this.effects != null) {
BakedGlyph bakedGlyph = Font.this.getFontSet(Style.DEFAULT_FONT).whiteGlyph();
VertexConsumer vertexConsumer = this.bufferSource.getBuffer(bakedGlyph.renderType(this.mode));
for (BakedGlyph.Effect effect : this.effects) {
bakedGlyph.renderEffect(effect, this.pose, vertexConsumer, this.packedLightCoords);
}
}
return this.x;
}
}
}