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 renderText(String text, float x, float y, float z) { if(text.isEmpty()) return ObjectLists.empty(); List drawCalls = new ObjectArrayList<>(); TextContext context = new TextContext(1F, 0); List 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 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 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 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 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