Added SpriteTypeface that implements text styling and layout
This commit is contained in:
parent
0e99685583
commit
54002475d0
@ -0,0 +1,312 @@
|
||||
package ru.windcorp.progressia.client.graphics.font;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import glm.vec._3.Vec3;
|
||||
import ru.windcorp.progressia.client.graphics.backend.Usage;
|
||||
import ru.windcorp.progressia.client.graphics.model.Face;
|
||||
import ru.windcorp.progressia.client.graphics.model.Faces;
|
||||
import ru.windcorp.progressia.client.graphics.model.Shape;
|
||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
|
||||
import ru.windcorp.progressia.client.graphics.model.WorldRenderable;
|
||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||
|
||||
public abstract class SpriteTypeface extends Typeface {
|
||||
|
||||
private final int height;
|
||||
private final int thickness;
|
||||
private final Vec3 shadowOffset;
|
||||
|
||||
public SpriteTypeface(String name, int height, int thinkness) {
|
||||
super(name);
|
||||
this.height = height;
|
||||
this.thickness = thinkness;
|
||||
this.shadowOffset = new Vec3(thickness, -thickness, 0);
|
||||
}
|
||||
|
||||
public abstract Texture getTexture(char c);
|
||||
|
||||
public int getWidth(char c) {
|
||||
return getTexture(c).getSprite().getWidth();
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public int getThickness() {
|
||||
return thickness;
|
||||
}
|
||||
|
||||
public float getItalicsSlant() {
|
||||
return getThickness() / (float) getHeight();
|
||||
}
|
||||
|
||||
public float getBoldOffset() {
|
||||
return getThickness();
|
||||
}
|
||||
|
||||
public Vec3 getShadowOffset() {
|
||||
return shadowOffset;
|
||||
}
|
||||
|
||||
public float getShadowColorMultiplier() {
|
||||
return 0.5f;
|
||||
}
|
||||
|
||||
public abstract ShapeRenderProgram getProgram();
|
||||
|
||||
@Override
|
||||
public WorldRenderable assemble(
|
||||
CharSequence chars, int style,
|
||||
float align, int maxWidth,
|
||||
int color
|
||||
) {
|
||||
ArrayList<Face> faces = new ArrayList<>();
|
||||
|
||||
long packedSize = getSize(chars, style, align, maxWidth);
|
||||
int resultWidth = getWidth(packedSize);
|
||||
int resultHeight = getHeight(packedSize);
|
||||
|
||||
int currentWidth = Style.isBold(style) ? getThickness() : 0;
|
||||
int y = resultHeight - getHeight();
|
||||
|
||||
int start = 0;
|
||||
for (int i = 0; i < chars.length(); ++i) {
|
||||
char c = chars.charAt(i);
|
||||
|
||||
if (c == '\n' || currentWidth + getWidth(c) > maxWidth) {
|
||||
|
||||
assembleLine(
|
||||
chars.subSequence(start, i),
|
||||
currentWidth, resultWidth, y,
|
||||
faces::add,
|
||||
style, align, color
|
||||
);
|
||||
|
||||
y -= getHeight();
|
||||
|
||||
currentWidth = Style.isBold(style) ? getThickness() : 0;
|
||||
|
||||
if (c == '\n') {
|
||||
start = i + 1; // Skip c
|
||||
} else {
|
||||
start = i; // Don't skip c
|
||||
}
|
||||
}
|
||||
|
||||
if (c != '\n') {
|
||||
currentWidth += getWidth(c);
|
||||
}
|
||||
}
|
||||
|
||||
assembleLine(
|
||||
chars.subSequence(start, chars.length()),
|
||||
currentWidth, resultWidth, y,
|
||||
faces::add,
|
||||
style, align, color
|
||||
);
|
||||
|
||||
return new Shape(
|
||||
Usage.STATIC, getProgram(),
|
||||
faces.toArray(new Face[faces.size()])
|
||||
);
|
||||
}
|
||||
|
||||
private class FaceSpec {
|
||||
final Texture texture;
|
||||
final Vec3 color;
|
||||
|
||||
final Vec3 origin;
|
||||
final Vec3 width;
|
||||
final Vec3 height;
|
||||
|
||||
FaceSpec(
|
||||
Texture texture, Vec3 color,
|
||||
Vec3 origin, Vec3 width, Vec3 height
|
||||
) {
|
||||
this.texture = texture;
|
||||
this.color = new Vec3(color);
|
||||
this.origin = new Vec3(origin);
|
||||
this.width = new Vec3(width);
|
||||
this.height = new Vec3(height);
|
||||
}
|
||||
|
||||
Face createFace() {
|
||||
return Faces.createRectangle(
|
||||
getProgram(),
|
||||
texture, color, origin, width, height
|
||||
);
|
||||
}
|
||||
|
||||
FaceSpec copy() {
|
||||
return new FaceSpec(texture, color, origin, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
private void assembleLine(
|
||||
CharSequence line,
|
||||
int currentWidth, int resultWidth, int y,
|
||||
Consumer<Face> output,
|
||||
int style, float align, int colorInt
|
||||
) {
|
||||
List<FaceSpec> faces = new ArrayList<>(line.length() * 2 + 2);
|
||||
|
||||
float startX = getStartX(currentWidth, resultWidth, align);
|
||||
Vec3 color = createVectorFromRGBInt(colorInt);
|
||||
|
||||
specifyCharacters(line, startX, y, color, faces::add);
|
||||
|
||||
if (Style.isBold(style)) {
|
||||
specifyBold(faces);
|
||||
}
|
||||
|
||||
if (Style.isUnderlined(style)) {
|
||||
specifyUnderline(faces::add, startX, y, currentWidth, color);
|
||||
}
|
||||
|
||||
if (Style.isStrikethru(style)) {
|
||||
specifyStrikethru(faces::add, startX, y, currentWidth, color);
|
||||
}
|
||||
|
||||
if (Style.isItalic(style)) {
|
||||
specifyItalic(faces, y);
|
||||
}
|
||||
|
||||
if (Style.hasShadow(style)) {
|
||||
specifyShadow(faces);
|
||||
}
|
||||
|
||||
faces.stream().map(FaceSpec::createFace).forEach(output);
|
||||
}
|
||||
|
||||
private float getStartX(int currentWidth, int resultWidth, float align) {
|
||||
return align * (resultWidth - currentWidth);
|
||||
}
|
||||
|
||||
private void specifyCharacters(
|
||||
CharSequence line,
|
||||
float startX, float y,
|
||||
Vec3 color,
|
||||
Consumer<FaceSpec> output
|
||||
) {
|
||||
Vec3 caret = new Vec3(startX, y, 0);
|
||||
Vec3 width = new Vec3(0);
|
||||
Vec3 height = new Vec3(0, getHeight(), 0);
|
||||
|
||||
for (int i = 0; i < line.length(); ++i) {
|
||||
char c = line.charAt(i);
|
||||
|
||||
Texture texture = getTexture(c);
|
||||
float charWidth = getWidth(c);
|
||||
|
||||
width.x = charWidth;
|
||||
|
||||
output.accept(new FaceSpec(texture, color, caret, width, height));
|
||||
|
||||
caret.x += charWidth;
|
||||
}
|
||||
}
|
||||
|
||||
private void specifyBold(List<FaceSpec> faces) {
|
||||
int size = faces.size();
|
||||
|
||||
for (int i = 0; i < size; ++i) {
|
||||
FaceSpec copy = faces.get(i).copy();
|
||||
copy.origin.x += getBoldOffset();
|
||||
faces.add(copy);
|
||||
}
|
||||
}
|
||||
|
||||
private void specifyUnderline(
|
||||
Consumer<FaceSpec> output,
|
||||
float startX, int y, int currentWidth,
|
||||
Vec3 color
|
||||
) {
|
||||
output.accept(new FaceSpec(
|
||||
null,
|
||||
color,
|
||||
new Vec3(startX, y, 0),
|
||||
new Vec3(currentWidth, 0, 0),
|
||||
new Vec3(0, getThickness(), 0)
|
||||
));
|
||||
}
|
||||
|
||||
private void specifyStrikethru(
|
||||
Consumer<FaceSpec> output,
|
||||
float startX, int y, int currentWidth,
|
||||
Vec3 color
|
||||
) {
|
||||
float startY = y + (getHeight() - getThickness()) / 2f;
|
||||
|
||||
output.accept(new FaceSpec(
|
||||
null,
|
||||
color,
|
||||
new Vec3(startX, startY, 0),
|
||||
new Vec3(currentWidth, 0, 0),
|
||||
new Vec3(0, getThickness(), 0)
|
||||
));
|
||||
}
|
||||
|
||||
private void specifyItalic(Collection<FaceSpec> faces, int y) {
|
||||
for (FaceSpec fs : faces) {
|
||||
fs.height.x += getItalicsSlant() * fs.height.y;
|
||||
fs.origin.x += getItalicsSlant() * (fs.origin.y - y);
|
||||
}
|
||||
}
|
||||
|
||||
private void specifyShadow(List<FaceSpec> faces) {
|
||||
int size = faces.size();
|
||||
|
||||
for (int i = 0; i < size; ++i) {
|
||||
FaceSpec copy = faces.get(i).copy();
|
||||
|
||||
copy.origin.add(getShadowOffset());
|
||||
copy.origin.z = 1;
|
||||
copy.color.mul(0.5f);
|
||||
|
||||
faces.add(copy);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long getSize(
|
||||
CharSequence chars, int style,
|
||||
float align, int maxWidth
|
||||
) {
|
||||
int resultWidth = 0;
|
||||
int currentWidth = Style.isBold(style) ? getThickness() : 0;
|
||||
int height = getHeight();
|
||||
|
||||
for (int i = 0; i < chars.length(); ++i) {
|
||||
char c = chars.charAt(i);
|
||||
|
||||
if (c == '\n' || currentWidth + getWidth(c) > maxWidth) {
|
||||
height += getHeight();
|
||||
if (resultWidth < currentWidth) resultWidth = currentWidth;
|
||||
currentWidth = Style.isBold(style) ? getThickness() : 0;
|
||||
}
|
||||
|
||||
if (c != '\n') {
|
||||
currentWidth += getWidth(c);
|
||||
}
|
||||
}
|
||||
|
||||
if (resultWidth < currentWidth) resultWidth = currentWidth;
|
||||
|
||||
return pack(resultWidth, height);
|
||||
}
|
||||
|
||||
// TODO remove
|
||||
private static Vec3 createVectorFromRGBInt(int rgb) {
|
||||
int r = (rgb & 0xFF0000) >> 16;
|
||||
int g = (rgb & 0x00FF00) >> 8;
|
||||
int b = (rgb & 0x0000FF);
|
||||
|
||||
return new Vec3(r / 256f, g / 256f, b / 256f);
|
||||
}
|
||||
|
||||
}
|
@ -3,15 +3,10 @@ package ru.windcorp.progressia.client.graphics.font;
|
||||
import java.io.IOException;
|
||||
|
||||
import glm.vec._2.Vec2;
|
||||
import glm.vec._3.Vec3;
|
||||
import gnu.trove.map.TCharObjectMap;
|
||||
import gnu.trove.map.hash.TCharObjectHashMap;
|
||||
import ru.windcorp.progressia.client.graphics.backend.Usage;
|
||||
import ru.windcorp.progressia.client.graphics.flat.FlatRenderProgram;
|
||||
import ru.windcorp.progressia.client.graphics.model.Face;
|
||||
import ru.windcorp.progressia.client.graphics.model.Faces;
|
||||
import ru.windcorp.progressia.client.graphics.model.Shape;
|
||||
import ru.windcorp.progressia.client.graphics.model.WorldRenderable;
|
||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
|
||||
import ru.windcorp.progressia.client.graphics.texture.SimpleTexture;
|
||||
import ru.windcorp.progressia.client.graphics.texture.Sprite;
|
||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||
@ -20,14 +15,12 @@ import ru.windcorp.progressia.client.graphics.texture.TexturePrimitive;
|
||||
import ru.windcorp.progressia.client.graphics.texture.TextureSettings;
|
||||
import ru.windcorp.progressia.common.resource.ResourceManager;
|
||||
|
||||
public class TestTypeface extends Typeface {
|
||||
public class TestTypeface extends SpriteTypeface {
|
||||
|
||||
private static final TCharObjectMap<Texture> TEXTURES = new TCharObjectHashMap<>();
|
||||
|
||||
private static final Vec3 SIZE = new Vec3(8, 8, 0);
|
||||
|
||||
public TestTypeface() {
|
||||
super("Test");
|
||||
super("Test", 8, 1);
|
||||
|
||||
TexturePrimitive primitive = null;
|
||||
try {
|
||||
@ -46,48 +39,20 @@ public class TestTypeface extends Typeface {
|
||||
TEXTURES.put('C', new SimpleTexture(new Sprite(primitive, new Vec2(0, 0), size)));
|
||||
TEXTURES.put('D', new SimpleTexture(new Sprite(primitive, new Vec2(0.5f, 0), size)));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public WorldRenderable assemble(
|
||||
CharSequence chars, int style,
|
||||
float align, int maxWidth,
|
||||
int color
|
||||
) {
|
||||
Face[] faces = new Face[chars.length()];
|
||||
|
||||
Vec3 caret = new Vec3();
|
||||
|
||||
for (int i = 0; i < chars.length(); ++i) {
|
||||
char c = chars.charAt(i);
|
||||
|
||||
if (supports(c)) {
|
||||
faces[i] = Faces.createRectangle(
|
||||
FlatRenderProgram.getDefault(),
|
||||
TEXTURES.get(c), new Vec3(1, 1, 1),
|
||||
caret, new Vec3(SIZE.x, 0, 0), new Vec3(0, SIZE.y, 0)
|
||||
);
|
||||
}
|
||||
|
||||
caret.x += SIZE.x;
|
||||
}
|
||||
|
||||
return new Shape(Usage.STATIC, FlatRenderProgram.getDefault(), faces);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long getSize(
|
||||
CharSequence chars, int style,
|
||||
float align, int maxWidth
|
||||
) {
|
||||
return pack(
|
||||
(int) (chars.length() * SIZE.x),
|
||||
(int) (SIZE.y)
|
||||
);
|
||||
public Texture getTexture(char c) {
|
||||
return TEXTURES.get(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(char c) {
|
||||
return TEXTURES.containsKey(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShapeRenderProgram getProgram() {
|
||||
return FlatRenderProgram.getDefault();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -75,9 +75,9 @@ public class LayerTestGUI extends GUILayer {
|
||||
|
||||
panel.addChild(new DebugComponent("Bravo", new Vec2i(200, 100), 0x44FF44));
|
||||
|
||||
Component charlie = new DebugComponent("Charlie", null, 0x4444FF);
|
||||
Component charlie = new DebugComponent("Charlie", null, 0x222222);
|
||||
charlie.setLayout(new LayoutAlign());
|
||||
charlie.addChild(new Label("Delta", new Font(), "BABCBABCBABC"));
|
||||
charlie.addChild(new Label("Delta", new Font().deriveItalic().deriveBold().withAlign(0.5f).withColor(0xCCBB44).deriveShadow().deriveUnderlined().deriveStrikethru(), "BABCB\nABCBABC"));
|
||||
panel.addChild(charlie);
|
||||
|
||||
getRoot().addChild(panel);
|
||||
|
@ -54,5 +54,13 @@ public class Sprite {
|
||||
public Vec2 getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return (int) (size.x * primitive.getBufferWidth());
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return (int) (size.y * primitive.getBufferHeight());
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user