445 lines
14 KiB
Java
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;
|
|
}
|
|
}
|
|
}
|