/*
* Copyright 2014 MovingBlocks
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.terasology.rendering.assets.font;
import com.google.common.collect.Maps;
import org.terasology.math.geom.Vector3f;
import org.terasology.rendering.FontColor;
import org.terasology.rendering.FontUnderline;
import org.terasology.rendering.assets.material.Material;
import org.terasology.rendering.assets.mesh.Mesh;
import org.terasology.rendering.assets.mesh.MeshBuilder;
import org.terasology.rendering.nui.Color;
import org.terasology.rendering.nui.HorizontalAlign;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;
import java.util.Map;
/**
*/
public class FontMeshBuilder {
private static final float SHADOW_DEPTH = -2;
private static final int SHADOW_HORIZONTAL_OFFSET = 1;
private static final int SHADOW_VERTICAL_OFFSET = 1;
private static final int UNKNOWN = -1;
private final Material underlineMaterial;
public FontMeshBuilder(Material underlineMaterial) {
this.underlineMaterial = underlineMaterial;
}
public Map<Material, Mesh> createTextMesh(Font font, List<String> lines, int width, HorizontalAlign alignment, Color baseColor, Color shadowColor, boolean underline) {
return new Builder(font, lines, width, alignment, baseColor, shadowColor, underline).invoke();
}
private class Builder {
private Font font;
private List<String> lines;
private int width;
private HorizontalAlign alignment;
private Color shadowColor;
private boolean baseUnderline;
private Map<Material, MeshBuilder> meshBuilders = Maps.newLinkedHashMap();
private int x;
private int y;
private boolean currentUnderline;
private int underlineStart = UNKNOWN;
private int underlineEnd = UNKNOWN;
private Deque<Color> previousColors = new ArrayDeque<>();
private Color currentColor;
Builder(Font font, List<String> lines, int width, HorizontalAlign alignment, Color baseColor, Color shadowColor, boolean baseUnderline) {
this.font = font;
this.lines = lines;
this.width = width;
this.alignment = alignment;
this.shadowColor = shadowColor;
this.baseUnderline = baseUnderline;
this.currentUnderline = baseUnderline;
this.currentColor = baseColor;
}
public Map<Material, Mesh> invoke() {
processLines();
return generateResult();
}
private Map<Material, Mesh> generateResult() {
Map<Material, Mesh> result = Maps.newLinkedHashMap();
for (Map.Entry<Material, MeshBuilder> entry : meshBuilders.entrySet()) {
result.put(entry.getKey(), entry.getValue().build());
}
return result;
}
private MeshBuilder getBuilderFor(Material material) {
MeshBuilder builder = meshBuilders.get(material);
if (builder == null) {
builder = new MeshBuilder();
meshBuilders.put(material, builder);
}
return builder;
}
private void processLines() {
for (String line : lines) {
int w = font.getWidth(line);
x = alignment.getOffset(w, width);
for (char c : line.toCharArray()) {
FontCharacter character = font.getCharacterData(c);
if (character != null && character.getPage() != null) {
MeshBuilder builder = getBuilderFor(character.getPageMat());
if (shadowColor.a() != 0) {
addCharacter(builder, character, shadowColor, SHADOW_HORIZONTAL_OFFSET, SHADOW_VERTICAL_OFFSET, SHADOW_DEPTH);
}
addCharacter(builder, character, currentColor, 0, 0, 0);
updateUnderline(c, character);
x += character.getxAdvance();
} else if (FontColor.isValid(c)) {
applyUnderline();
processColorCode(c);
} else if (FontUnderline.isValid(c)) {
processUnderlineCode(c);
}
}
applyUnderline();
y += font.getLineHeight();
}
}
private void processUnderlineCode(char c) {
if (!baseUnderline) {
if (c == FontUnderline.getStart() && !currentUnderline) {
currentUnderline = true;
} else if (currentUnderline) {
applyUnderline();
currentUnderline = false;
}
}
}
private void processColorCode(char c) {
if (c == FontColor.getReset()) {
if (!previousColors.isEmpty()) {
currentColor = previousColors.removeLast();
}
} else {
previousColors.addLast(currentColor);
currentColor = FontColor.toColor(c);
}
}
private void applyUnderline() {
if (currentUnderline && underlineStart != UNKNOWN) {
MeshBuilder builder = getBuilderFor(underlineMaterial);
if (shadowColor.a() != 0) {
addUnderline(builder, underlineStart + SHADOW_HORIZONTAL_OFFSET, underlineEnd + SHADOW_HORIZONTAL_OFFSET,
y + font.getBaseHeight() + SHADOW_VERTICAL_OFFSET + font.getUnderlineOffset(), font.getUnderlineThickness(), shadowColor, SHADOW_DEPTH);
}
addUnderline(builder, underlineStart, underlineEnd, y + font.getBaseHeight() + font.getUnderlineOffset(), font.getUnderlineThickness(), currentColor, 0);
}
underlineStart = UNKNOWN;
underlineEnd = UNKNOWN;
}
private void updateUnderline(char c, FontCharacter character) {
if (currentUnderline && !Character.isWhitespace(c)) {
if (underlineStart == UNKNOWN) {
underlineStart = x + character.getxOffset();
}
underlineEnd = x + character.getxOffset() + character.getWidth();
}
}
private void addUnderline(MeshBuilder builder, int xStart, int xEnd, int underlineTop, int underlineThickness, Color color, float depth) {
float bottom = (float) underlineTop + underlineThickness;
Vector3f v1 = new Vector3f(xStart, underlineTop, depth);
Vector3f v2 = new Vector3f(xEnd, underlineTop, depth);
Vector3f v3 = new Vector3f(xEnd, bottom, depth);
Vector3f v4 = new Vector3f(xStart, bottom, depth);
builder.addPoly(v1, v2, v3, v4);
builder.addColor(color, color, color, color);
builder.addTexCoord(0, 0);
builder.addTexCoord(1, 0);
builder.addTexCoord(1, 1);
builder.addTexCoord(0, 1);
}
private void addCharacter(MeshBuilder builder, FontCharacter character, Color color, float xOffset, float yOffset, float depth) {
float top = y + character.getyOffset() + yOffset;
float bottom = top + character.getHeight() + yOffset;
float left = x + character.getxOffset() + xOffset;
float right = left + character.getWidth() + xOffset;
float texTop = character.getY();
float texBottom = texTop + character.getTexHeight();
float texLeft = character.getX();
float texRight = texLeft + character.getTexWidth();
Vector3f v1 = new Vector3f(left, top, depth);
Vector3f v2 = new Vector3f(right, top, depth);
Vector3f v3 = new Vector3f(right, bottom, depth);
Vector3f v4 = new Vector3f(left, bottom, depth);
builder.addPoly(v1, v2, v3, v4);
builder.addColor(color, color, color, color);
builder.addTexCoord(texLeft, texTop);
builder.addTexCoord(texRight, texTop);
builder.addTexCoord(texRight, texBottom);
builder.addTexCoord(texLeft, texBottom);
}
}
}