447 lines
15 KiB
Java
447 lines
15 KiB
Java
package speiger.src.coreengine.rendering.gui.renderer;
|
|
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
|
|
import org.lwjgl.opengl.GL11;
|
|
|
|
import speiger.src.collections.floats.lists.FloatArrayList;
|
|
import speiger.src.collections.floats.lists.FloatList;
|
|
import speiger.src.collections.objects.lists.ObjectArrayList;
|
|
import speiger.src.collections.objects.utils.ObjectLists;
|
|
import speiger.src.coreengine.rendering.gui.components.TextComponent;
|
|
import speiger.src.coreengine.rendering.gui.helper.Align;
|
|
import speiger.src.coreengine.rendering.gui.renderer.buffer.DelayedRenderBuffer;
|
|
import speiger.src.coreengine.rendering.gui.renderer.buffer.RenderBuffer;
|
|
import speiger.src.coreengine.rendering.gui.renderer.buffer.TranslatedVertexBuilder;
|
|
import speiger.src.coreengine.rendering.gui.renderer.lexer.Line;
|
|
import speiger.src.coreengine.rendering.gui.renderer.lexer.TextContext;
|
|
import speiger.src.coreengine.rendering.gui.renderer.lexer.TextContext.WordContext;
|
|
import speiger.src.coreengine.rendering.gui.renderer.lexer.TextLexer;
|
|
import speiger.src.coreengine.rendering.gui.renderer.provider.IFontProvider;
|
|
import speiger.src.coreengine.rendering.models.DrawCall;
|
|
import speiger.src.coreengine.rendering.tesselation.IVertexBuilder;
|
|
import speiger.src.coreengine.rendering.tesselation.Tesselator;
|
|
import speiger.src.coreengine.rendering.tesselation.VertexType;
|
|
import speiger.src.coreengine.rendering.textures.base.ITexture;
|
|
import speiger.src.coreengine.utils.helpers.TextUtil;
|
|
|
|
public class FontRenderer implements IFontRenderer
|
|
{
|
|
public static final String INVALID_SEARCH = new String(" §<");
|
|
public static final int EMPTY = (char)0;
|
|
public static final int SPACE = ' ';
|
|
public static final int TAB = '\t';
|
|
public static final int LINE_SEPERATOR = '\n';
|
|
Tesselator bufferBuilder = new Tesselator(655340);
|
|
|
|
IFontProvider provider;
|
|
final TextLexer lexer = new TextLexer(this);
|
|
final DelayedRenderBuffer lineBuffer = new DelayedRenderBuffer();
|
|
|
|
public void setProvider(IFontProvider provider)
|
|
{
|
|
this.provider = provider;
|
|
}
|
|
|
|
@Override
|
|
public CharInstance getInstance(int codepoint, boolean isBold)
|
|
{
|
|
return provider.getCharacter(codepoint, isBold);
|
|
}
|
|
|
|
@Override
|
|
public float height()
|
|
{
|
|
return provider.height();
|
|
}
|
|
|
|
@Override
|
|
public float baseLine()
|
|
{
|
|
return provider.baseLine();
|
|
}
|
|
|
|
@Override
|
|
public ITexture getTexture()
|
|
{
|
|
return provider.getTexture();
|
|
}
|
|
|
|
public IFontProvider getProvider()
|
|
{
|
|
return provider;
|
|
}
|
|
|
|
public void destory()
|
|
{
|
|
if(provider != null)
|
|
{
|
|
provider.destroy();
|
|
provider = null;
|
|
}
|
|
}
|
|
|
|
public List<DrawCall> renderText(String text, float x, float y, float z)
|
|
{
|
|
if(text.isEmpty()) return ObjectLists.empty();
|
|
List<DrawCall> drawCalls = new ObjectArrayList<>();
|
|
TextContext context = new TextContext(1F, 0);
|
|
List<Line> lines = lexer.evaluateLines(text, context, Float.MAX_VALUE);
|
|
if(lines.isEmpty()) return ObjectLists.empty();
|
|
WordContext effects = context.getEffect();
|
|
int textColor = effects.color;
|
|
float yOffset = 0F;
|
|
IVertexBuilder builder = new TranslatedVertexBuilder(bufferBuilder, x, y, z, 1F);
|
|
bufferBuilder.begin(GL11.GL_TRIANGLES, VertexType.IN_WORLD_UI);
|
|
for(int i = 0,m=lines.size();i<m;i++)
|
|
{
|
|
float xOffset = 9F;
|
|
for(CharInstance letter : lines.get(i).letterIterator())
|
|
{
|
|
xOffset += renderChar(letter, xOffset, yOffset, context.getScale(), effects.italic, effects.flipped, textColor, builder, true);
|
|
}
|
|
yOffset += height() * context.getScale();
|
|
}
|
|
bufferBuilder.finishData();
|
|
if(bufferBuilder.getVertexCount() > 0)
|
|
{
|
|
drawCalls.add(bufferBuilder.getDrawCall(getTexture().getTextureId()));
|
|
}
|
|
bufferBuilder.setOffset(0F, 0F, 0F);
|
|
return drawCalls;
|
|
}
|
|
|
|
@Override
|
|
public void updateText(TextComponent component)
|
|
{
|
|
RenderBuffer buffer = component.getBuffer();
|
|
buffer.clear();
|
|
if(component.getText().isEmpty())
|
|
{
|
|
return;
|
|
}
|
|
TextContext context = new TextContext(component);
|
|
float boxWidth = component.getBox().getWidth();
|
|
float boxHeight = component.getBox().getHeight();
|
|
List<Line> lines = lexer.evaluateLines(component.getText(), context, component.isWidthLimited() ? boxWidth : Float.MAX_VALUE);
|
|
if(lines.isEmpty())
|
|
{
|
|
return;
|
|
}
|
|
bufferBuilder.begin(GL11.GL_TRIANGLES, VertexType.UI);
|
|
int maxLanes = component.isHeightLimited() ? Math.min((int)(boxHeight / (height() * context.getScale())), lines.size()) : lines.size();
|
|
float maxHeight = maxLanes * height() * context.getScale();
|
|
float maxWidth = 0F;
|
|
float yOffset = component.getVertical().align(boxHeight, maxHeight);
|
|
float startX = component.getHorizontal().align(boxWidth, lines.get(0).getWidth());
|
|
component.getMetadata().setStart(startX, yOffset);
|
|
WordContext effects = context.getEffect();
|
|
while(effects.isNext(0))
|
|
{
|
|
effects = effects.next();
|
|
}
|
|
int textColor = effects.color;
|
|
Float strikeThrough = effects.strike_through ? startX : null;
|
|
Float underline = effects.underline ? startX : -1F;
|
|
if(component.getBackgroundColor() != null)
|
|
{
|
|
Tesselator tes = buffer.start(GL11.GL_TRIANGLES, VertexType.UI).setOffset(0F, 0F, -0.001F);
|
|
addBackground(tes, lines, maxLanes, yOffset, component.getHorizontal(), boxWidth, component.getBackgroundColor(), false);
|
|
tes.setOffset(0F, 0F, 0F);
|
|
buffer.finishShape(0);
|
|
}
|
|
for(int i = 0, index = 0;i < maxLanes;i++)
|
|
{
|
|
float xOffset = component.getHorizontal().align(boxWidth, lines.get(i).getWidth());
|
|
maxWidth = Math.max(maxWidth, lines.get(i).getWidth());
|
|
strikeThrough = effects.strike_through ? xOffset : null;
|
|
underline = effects.underline ? xOffset : null;
|
|
for(CharInstance letter : lines.get(i).letterIterator())
|
|
{
|
|
xOffset += renderChar(letter, xOffset, yOffset, context.getScale(), effects.italic, effects.flipped, textColor, bufferBuilder, false);
|
|
if(effects.isNext(++index))
|
|
{
|
|
WordContext next = effects.next();
|
|
Float newStrike = getValue(effects.strike_through, next.strike_through, xOffset, strikeThrough);
|
|
if(newStrike == null && strikeThrough != null)
|
|
{
|
|
addStrikeThrough(strikeThrough, xOffset - strikeThrough, yOffset, textColor, lineBuffer);
|
|
}
|
|
strikeThrough = newStrike;
|
|
Float newLine = getValue(effects.underline, next.underline, xOffset, strikeThrough);
|
|
if(newLine == null && underline != null)
|
|
{
|
|
addUnderline(underline, xOffset - underline, yOffset, textColor, lineBuffer, false);
|
|
}
|
|
textColor = next.color;
|
|
underline = newLine;
|
|
effects = next;
|
|
}
|
|
}
|
|
if(strikeThrough != null)
|
|
{
|
|
addStrikeThrough(strikeThrough, xOffset - strikeThrough, yOffset, textColor, lineBuffer);
|
|
}
|
|
if(underline != null)
|
|
{
|
|
addUnderline(underline, xOffset - underline, yOffset, textColor, lineBuffer, false);
|
|
}
|
|
yOffset += height() * context.getScale();
|
|
component.getMetadata().addLine(lines.get(i));
|
|
}
|
|
maxWidth /= 2;
|
|
buffer.finishShape(getTexture().getTextureId(), bufferBuilder);
|
|
if(lineBuffer.hasData())
|
|
{
|
|
Tesselator tes = buffer.start(GL11.GL_TRIANGLES, VertexType.UI).offset(0F, 0F, 0.001F);
|
|
lineBuffer.merge(tes);
|
|
tes.setOffset(0F, 0F, 0F);
|
|
buffer.finishShape(0);
|
|
}
|
|
}
|
|
|
|
protected float renderChar(CharInstance instance, float x, float y, float scale, float italic, boolean flipped, int color, IVertexBuilder buffer, boolean flipPos)
|
|
{
|
|
switch(instance.getCharacter())
|
|
{
|
|
case TAB:
|
|
return provider.getTabWidth() * scale;
|
|
case SPACE:
|
|
return provider.getSpaceWidth() * scale;
|
|
}
|
|
if(instance.getXAdvance() <= 0F)
|
|
{
|
|
return 0F;
|
|
}
|
|
if(flipPos) flipped = !flipped;
|
|
float minX = x;
|
|
float minY = y;
|
|
float maxX = x + (instance.getWidth() * scale);
|
|
float maxY = flipPos ? y - (instance.getHeight() * scale) : y + (instance.getHeight() * scale);
|
|
float minV = flipped ? instance.getMaxV() : instance.getMinV();
|
|
float maxV = flipped ? instance.getMinV() : instance.getMaxV();
|
|
buffer.pos(minX - italic, maxY, 0F).tex(instance.getMinU(), maxV).color4f(color).endVertex();
|
|
buffer.pos(minX + italic, minY, 0F).tex(instance.getMinU(), minV).color4f(color).endVertex();
|
|
buffer.pos(maxX - italic, maxY, 0F).tex(instance.getMaxU(), maxV).color4f(color).endVertex();
|
|
buffer.pos(maxX - italic, maxY, 0F).tex(instance.getMaxU(), maxV).color4f(color).endVertex();
|
|
buffer.pos(minX + italic, minY, 0F).tex(instance.getMinU(), minV).color4f(color).endVertex();
|
|
buffer.pos(maxX + italic, minY, 0F).tex(instance.getMaxU(), minV).color4f(color).endVertex();
|
|
return instance.getXAdvance() * scale;
|
|
}
|
|
|
|
protected void addBackground(IVertexBuilder tes, List<Line> lines, int maxLines, float yPos, Align align, float width, int color, boolean flipPos)
|
|
{
|
|
for(int i = 0;i < maxLines;i++)
|
|
{
|
|
float lineWidth = lines.get(i).getWidth();
|
|
float xOffset = align.align(width, lineWidth);
|
|
float maxY = flipPos ? yPos - height() : yPos + height();
|
|
tes.pos(xOffset, maxY, 0.0F).tex(0F, 0F).color4f(color).endVertex();
|
|
tes.pos(xOffset, yPos, 0.0F).tex(0F, 0F).color4f(color).endVertex();
|
|
tes.pos(xOffset + lineWidth, maxY, 0.0F).tex(0F, 0F).color4f(color).endVertex();
|
|
tes.pos(xOffset + lineWidth, maxY, 0.0F).tex(0F, 0F).color4f(color).endVertex();
|
|
tes.pos(xOffset, yPos, 0.0F).tex(0F, 0F).color4f(color).endVertex();
|
|
tes.pos(xOffset + lineWidth, yPos, 0.0F).tex(0F, 0F).color4f(color).endVertex();
|
|
yPos = maxY;
|
|
}
|
|
}
|
|
|
|
protected void addUnderline(float xStart, float width, float yStart, int color, IVertexBuilder buffer, boolean flipPos)
|
|
{
|
|
float minY = yStart + baseLine() + 0.5F;
|
|
float maxY = yStart + baseLine() + 1.5F;
|
|
if(flipPos)
|
|
{
|
|
minY = yStart - baseLine() - 0.5F;
|
|
maxY = yStart - baseLine() - 1.5F;
|
|
}
|
|
buffer.pos(xStart, maxY, 0F).tex(0F, 0F).color4f(color).endVertex();
|
|
buffer.pos(xStart, minY, 0F).tex(0F, 0F).color4f(color).endVertex();
|
|
buffer.pos(xStart + width, maxY, 0F).tex(0F, 0F).color4f(color).endVertex();
|
|
buffer.pos(xStart + width, maxY, 0F).tex(0F, 0F).color4f(color).endVertex();
|
|
buffer.pos(xStart, minY, 0F).tex(0F, 0F).color4f(color).endVertex();
|
|
buffer.pos(xStart + width, minY, 0F).tex(0F, 0F).color4f(color).endVertex();
|
|
}
|
|
|
|
protected void addStrikeThrough(float xStart, float width, float yStart, int color, IVertexBuilder buffer)
|
|
{
|
|
float minY = yStart + height() / 2.0F;
|
|
float maxY = yStart + height() / 2.0F + 1.4F;
|
|
buffer.pos(xStart, maxY, 0.0F).tex(0F, 0F).color4f(color).endVertex();
|
|
buffer.pos(xStart, minY, 0.0F).tex(0F, 0F).color4f(color).endVertex();
|
|
buffer.pos(xStart + width, maxY, 0.0F).tex(0F, 0F).color4f(color).endVertex();
|
|
buffer.pos(xStart + width, maxY, 0.0F).tex(0F, 0F).color4f(color).endVertex();
|
|
buffer.pos(xStart, minY, 0.0F).tex(0F, 0F).color4f(color).endVertex();
|
|
buffer.pos(xStart + width, minY, 0.0F).tex(0F, 0F).color4f(color).endVertex();
|
|
}
|
|
|
|
protected Float getValue(boolean oldValue, boolean newValue, Float position, Float oldPosition)
|
|
{
|
|
if((position == null && oldPosition == null) || (position != null && oldPosition != null)) return oldPosition;
|
|
return newValue ? position : null;
|
|
}
|
|
|
|
@Override
|
|
public String trimToWidth(String text, float limit, boolean reverse)
|
|
{
|
|
StringBuilder builder = new StringBuilder();
|
|
float width = 0F;
|
|
int start = reverse ? text.length() - 1 : 0;
|
|
int direction = reverse ? -1 : 1;
|
|
for(int i = start;i >= 0 && i < text.length() && width < limit;i += direction)
|
|
{
|
|
char letter = text.charAt(i);
|
|
width += width(letter);
|
|
if(width > limit) break;
|
|
if(reverse) builder.insert(0, letter);
|
|
else builder.append(letter);
|
|
}
|
|
return builder.toString();
|
|
}
|
|
|
|
@Override
|
|
public float width(int codepoint, boolean bold)
|
|
{
|
|
switch(codepoint)
|
|
{
|
|
case SPACE:
|
|
return provider.getSpaceWidth();
|
|
case TAB:
|
|
return provider.getTabWidth();
|
|
default:
|
|
CharInstance instance = getInstance(codepoint, bold);
|
|
return instance == null ? 0F : instance.getXAdvance();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public float width(String text, int flags)
|
|
{
|
|
float result = 0.0F;
|
|
float current = 0.0F;
|
|
boolean bold = (flags & BOLD) != 0;
|
|
for(int i = 0;i < text.length();i++)
|
|
{
|
|
char character = text.charAt(i);
|
|
if(LINE_SEPERATOR == character)
|
|
{
|
|
result = Math.max(result, current += provider.getSpaceWidth());
|
|
current = 0.0F;
|
|
continue;
|
|
}
|
|
if(character == '§' && (flags & SPECIAL) != 0 && i + 1 < text.length() && text.charAt(i + 1) == '<')
|
|
{
|
|
String search = TextUtil.searchUntil(text, i + 2, '>', INVALID_SEARCH);
|
|
if(search.length() > 0)
|
|
{
|
|
for(String entry : search.toLowerCase(Locale.ROOT).split(","))
|
|
{
|
|
bold = TextUtil.findFlag(entry, "bold", bold);
|
|
}
|
|
i += search.length() + 2;
|
|
continue;
|
|
}
|
|
}
|
|
current += width(character, bold);
|
|
}
|
|
return Math.max(result, current);
|
|
}
|
|
|
|
@Override
|
|
public float[] widths(String text, int flags)
|
|
{
|
|
FloatList results = new FloatArrayList();
|
|
results.add(0F);
|
|
float current = 0F;
|
|
int chars = 0;
|
|
boolean bold = (flags & BOLD) != 0;
|
|
float max = 0F;
|
|
for(int i = 0;i < text.length();i++)
|
|
{
|
|
char character = text.charAt(i);
|
|
if(LINE_SEPERATOR == character)
|
|
{
|
|
results.add(current);
|
|
max = Math.max(max, current);
|
|
current = 0F;
|
|
chars = 0;
|
|
continue;
|
|
}
|
|
chars++;
|
|
if(character == '§' && (flags & SPECIAL) != 0 && i + 1 < text.length() && text.charAt(i + 1) == '<')
|
|
{
|
|
String result = TextUtil.searchUntil(text, i + 2, '>', INVALID_SEARCH);
|
|
if(result.length() > 0)
|
|
{
|
|
for(String entry : result.toLowerCase(Locale.ROOT).split(","))
|
|
{
|
|
bold = TextUtil.findFlag(entry, "bold", bold);
|
|
}
|
|
i += result.length() + 2;
|
|
continue;
|
|
}
|
|
}
|
|
current += width(character, bold);
|
|
}
|
|
if(chars++ > 0)
|
|
{
|
|
results.add(current);
|
|
max = Math.max(max, current);
|
|
}
|
|
results.set(0, max);
|
|
return results.toFloatArray();
|
|
}
|
|
|
|
@Override
|
|
public float height(String text, int flags)
|
|
{
|
|
return widths(text, flags).length - 1 * height();
|
|
}
|
|
|
|
@Override
|
|
public boolean isCharValid(int codepoint)
|
|
{
|
|
return provider.isCharacterValid(codepoint);
|
|
}
|
|
|
|
@Override
|
|
public String[] split(String text, float maxWidth, int flags)
|
|
{
|
|
TextContext context = new TextContext(1F, ((flags & IFontRenderer.SPECIAL) != 0 ? 16 : 0) | ((flags & IFontRenderer.BOLD) != 0 ? 1 : 0));
|
|
List<Line> lines = lexer.evaluateLines(text, context, maxWidth);
|
|
String[] array = new String[lines.size()];
|
|
if(context.allowsSpecial())
|
|
{
|
|
int index = 0;
|
|
StringBuilder builder = new StringBuilder();
|
|
WordContext wordContext = context.getEffect();
|
|
while(wordContext.isNext(0))
|
|
{
|
|
builder.append(wordContext.getString(false));
|
|
wordContext = wordContext.next();
|
|
}
|
|
for(int i = 0,m=lines.size();i<m;i++)
|
|
{
|
|
if(i != 0) builder.append(wordContext.getString(true));
|
|
for(CharInstance letter : lines.get(i).letterIterator())
|
|
{
|
|
builder.append(letter.getCharacter());
|
|
if(wordContext.isNext(++index))
|
|
{
|
|
wordContext = wordContext.next();
|
|
builder.append(wordContext.getString(false));
|
|
}
|
|
}
|
|
array[i] = builder.toString();
|
|
builder.setLength(0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for(int i = 0,m=lines.size();i<m;i++) array[i] = lines.get(i).getText();
|
|
}
|
|
return array;
|
|
}
|
|
}
|