Added SpriteTypeface that implements text styling and layout

This commit is contained in:
OLEGSHA 2020-08-16 20:37:42 +04:00
parent 0e99685583
commit 54002475d0
4 changed files with 333 additions and 48 deletions

View File

@ -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);
}
}

View File

@ -3,15 +3,10 @@ package ru.windcorp.progressia.client.graphics.font;
import java.io.IOException; import java.io.IOException;
import glm.vec._2.Vec2; import glm.vec._2.Vec2;
import glm.vec._3.Vec3;
import gnu.trove.map.TCharObjectMap; import gnu.trove.map.TCharObjectMap;
import gnu.trove.map.hash.TCharObjectHashMap; 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.flat.FlatRenderProgram;
import ru.windcorp.progressia.client.graphics.model.Face; import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
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.texture.SimpleTexture; import ru.windcorp.progressia.client.graphics.texture.SimpleTexture;
import ru.windcorp.progressia.client.graphics.texture.Sprite; import ru.windcorp.progressia.client.graphics.texture.Sprite;
import ru.windcorp.progressia.client.graphics.texture.Texture; 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.client.graphics.texture.TextureSettings;
import ru.windcorp.progressia.common.resource.ResourceManager; 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 TCharObjectMap<Texture> TEXTURES = new TCharObjectHashMap<>();
private static final Vec3 SIZE = new Vec3(8, 8, 0);
public TestTypeface() { public TestTypeface() {
super("Test"); super("Test", 8, 1);
TexturePrimitive primitive = null; TexturePrimitive primitive = null;
try { try {
@ -48,41 +41,8 @@ public class TestTypeface extends Typeface {
} }
@Override @Override
public WorldRenderable assemble( public Texture getTexture(char c) {
CharSequence chars, int style, return TEXTURES.get(c);
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)
);
} }
@Override @Override
@ -90,4 +50,9 @@ public class TestTypeface extends Typeface {
return TEXTURES.containsKey(c); return TEXTURES.containsKey(c);
} }
@Override
public ShapeRenderProgram getProgram() {
return FlatRenderProgram.getDefault();
}
} }

View File

@ -75,9 +75,9 @@ public class LayerTestGUI extends GUILayer {
panel.addChild(new DebugComponent("Bravo", new Vec2i(200, 100), 0x44FF44)); 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.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); panel.addChild(charlie);
getRoot().addChild(panel); getRoot().addChild(panel);

View File

@ -55,4 +55,12 @@ public class Sprite {
return size; return size;
} }
public int getWidth() {
return (int) (size.x * primitive.getBufferWidth());
}
public int getHeight() {
return (int) (size.y * primitive.getBufferHeight());
}
} }