SimpleJavaEngine/src/main/java/speiger/src/coreengine/rendering/gui/renderer/FontRenderer.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;
}
}