Add TOML font loading support and refactor font handling
This commit is contained in:
@@ -34,6 +34,9 @@ dependencies {
|
||||
testImplementation("io.avaje:avaje-inject-test:${avajeInjectVersion}")
|
||||
testAnnotationProcessor("io.avaje:avaje-inject-generator:${avajeInjectVersion}")
|
||||
|
||||
// Parsing TOML files
|
||||
implementation("tools.jackson.dataformat:jackson-dataformat-toml:3.0.2")
|
||||
|
||||
// LibGDX
|
||||
api("com.badlogicgames.gdx:gdx:$gdxVersion")
|
||||
api("com.badlogicgames.gdx:gdx-backend-lwjgl3:$gdxVersion")
|
||||
|
||||
@@ -45,7 +45,6 @@ public class DiceOS extends ApplicationAdapter {
|
||||
vmViewport = new ScreenViewport();
|
||||
vmViewport.update(display.getWidth(), display.getHeight(), true);
|
||||
|
||||
|
||||
for (var task : startupTasks) {
|
||||
task.onStartup();
|
||||
}
|
||||
@@ -83,22 +82,21 @@ public class DiceOS extends ApplicationAdapter {
|
||||
ScreenUtils.clear(Color.GREEN);
|
||||
|
||||
// Render background
|
||||
display.draw(clouds, 0, 0);
|
||||
gc.draw(clouds, 0, 0);
|
||||
|
||||
windowService.paint(gc);
|
||||
|
||||
// Render borders
|
||||
var param = new DisplayService.Parameters();
|
||||
display.draw(border, 0, 0, param);
|
||||
var param = new GraphicsContext.Parameters();
|
||||
gc.draw(border, 0, 0, param);
|
||||
param.flipX = true;
|
||||
display.draw(border, display.getWidth() - 8, 0, param);
|
||||
gc.draw(border, display.getWidth() - 8, 0, param);
|
||||
param.flipY = true;
|
||||
display.draw(border, display.getWidth() - 8, display.getHeight() - 8, param);
|
||||
gc.draw(border, display.getWidth() - 8, display.getHeight() - 8, param);
|
||||
param.flipX = false;
|
||||
display.draw(border, 0, display.getHeight() - 8, param);
|
||||
gc.draw(border, 0, display.getHeight() - 8, param);
|
||||
|
||||
font.setColor(1, 1, 1, 0.5f);
|
||||
font.draw(display.getBatch(), "DiceOS", 50, display.getHeight() - 40);
|
||||
font.draw(display.getBatch(), "This is DiceOS", 6, display.getHeight() - 12);
|
||||
|
||||
// Finish rendering to screen buffer
|
||||
display.getBatch().end();
|
||||
|
||||
@@ -41,22 +41,4 @@ public class DisplayService implements OnStartup {
|
||||
public int getHeight() {
|
||||
return 360;
|
||||
}
|
||||
|
||||
public void draw(Texture texture, int x, int y) {
|
||||
draw(texture, x, y, Parameters.DEFAULT);
|
||||
}
|
||||
|
||||
public void draw(Texture texture, int x, int y, Parameters params) {
|
||||
batch.draw(texture,
|
||||
x, getHeight() - y - texture.getHeight(), texture.getWidth(), texture.getHeight(),
|
||||
0, 0, texture.getWidth(), texture.getHeight(),
|
||||
params.flipX, params.flipY);
|
||||
}
|
||||
|
||||
public static class Parameters {
|
||||
public final static Parameters DEFAULT = new Parameters();
|
||||
|
||||
public boolean flipX = false;
|
||||
public boolean flipY = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
package be.seeseemelk.diceos.system;
|
||||
|
||||
import be.seeseemelk.diceos.system.utils.Map2D;
|
||||
import be.seeseemelk.diceos.system.font.FontLoader;
|
||||
import be.seeseemelk.diceos.system.font.FontMetadata;
|
||||
import be.seeseemelk.diceos.system.gfx.NinePatchLoader;
|
||||
import com.badlogic.gdx.Gdx;
|
||||
import com.badlogic.gdx.graphics.Pixmap;
|
||||
import com.badlogic.gdx.graphics.Texture;
|
||||
import com.badlogic.gdx.graphics.g2d.BitmapFont;
|
||||
import com.badlogic.gdx.graphics.g2d.TextureRegion;
|
||||
import com.badlogic.gdx.graphics.g2d.NinePatch;
|
||||
import io.avaje.inject.Component;
|
||||
import lombok.Cleanup;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
import tools.jackson.dataformat.toml.TomlFactory;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -20,7 +24,9 @@ import java.util.Map;
|
||||
public class ResourceLoader {
|
||||
private final Map<String, Pixmap> pixmaps = new HashMap<>();
|
||||
private final Map<String, Texture> textures = new HashMap<>();
|
||||
private final Map<String, NinePatch> ninePatches = new HashMap<>();
|
||||
private final Map<String, BitmapFont> fonts = new HashMap<>();
|
||||
private final ObjectMapper mapper = new ObjectMapper(new TomlFactory());
|
||||
|
||||
public Pixmap loadPixmap(String path) {
|
||||
return pixmaps.computeIfAbsent(path, p -> new Pixmap(Gdx.files.internal(p)));
|
||||
@@ -30,6 +36,14 @@ public class ResourceLoader {
|
||||
return textures.computeIfAbsent(path, p -> new Texture(Gdx.files.internal(p)));
|
||||
}
|
||||
|
||||
public NinePatch loadNinePatch(String path) {
|
||||
return ninePatches.computeIfAbsent(path, p -> {
|
||||
var pixmap = loadPixmap(p);
|
||||
var loader = new NinePatchLoader(pixmap);
|
||||
return loader.getNinePatch();
|
||||
});
|
||||
}
|
||||
|
||||
public BitmapFont loadFont(String path) {
|
||||
return fonts.computeIfAbsent(path, this::loadBitmapfont);
|
||||
}
|
||||
@@ -37,186 +51,9 @@ public class ResourceLoader {
|
||||
private BitmapFont loadBitmapfont(String path) {
|
||||
log.info("Loading bitmap font from {}", path);
|
||||
|
||||
@Cleanup("dispose") var pixmap = loadPixmap(path + ".png");
|
||||
var texture = new Texture(pixmap);
|
||||
var region = new TextureRegion(texture);
|
||||
|
||||
var data = new BitmapFont.BitmapFontData();
|
||||
|
||||
var glyphMap = new Map2D<Integer, Integer, BitmapFont.Glyph>();
|
||||
var glyphLines = Gdx.files.internal(path + ".txt").readString().split("\n");
|
||||
for (var y = 0; y < glyphLines.length; y++) {
|
||||
var line = glyphLines[y].toCharArray();
|
||||
for (var x = 0; x < line.length; x++) {
|
||||
var glyph = new BitmapFont.Glyph();
|
||||
glyph.id = line[x];
|
||||
glyphMap.put(x, y, glyph);
|
||||
data.setGlyph(glyph.id, glyph);
|
||||
}
|
||||
}
|
||||
|
||||
// Read the vertical timing lines to find the horizontal ones and read glyph heights
|
||||
VerticalTiming[] glyphHeights = readGlyphHeights(pixmap, glyphLines.length);
|
||||
// Read the horizontal timing lines to read glyph widths
|
||||
HorizontalTiming[][] glyphWidths = new HorizontalTiming[glyphLines.length][];
|
||||
for (var y = 0; y < glyphHeights.length; y++) {
|
||||
glyphWidths[y] = readHorizontalTimings(pixmap, glyphLines[y].length(), glyphHeights[y].timingLine);
|
||||
}
|
||||
|
||||
// Assign glyph sizes
|
||||
for (var y = 0; y < glyphLines.length; y++) {
|
||||
for (var x = 0; x < glyphLines[y].length(); x++) {
|
||||
var glyph = glyphMap.get(x, y);
|
||||
var widthTiming = glyphWidths[y][x];
|
||||
var heightTiming = glyphHeights[y];
|
||||
glyph.width = widthTiming.getGlyphWidth();
|
||||
glyph.height = heightTiming.getGlyphHeight();
|
||||
glyph.srcX = widthTiming.glyphStart;
|
||||
glyph.srcY = heightTiming.glyphStart;
|
||||
glyph.xoffset = 0;
|
||||
glyph.yoffset = 0;
|
||||
glyph.xadvance = glyph.width;
|
||||
glyph.u = (float)widthTiming.glyphStart / pixmap.getWidth();
|
||||
glyph.u2 = (float)widthTiming.glyphEnd / pixmap.getWidth();
|
||||
glyph.v = (float)heightTiming.glyphStart / pixmap.getHeight();
|
||||
glyph.v2 = (float)heightTiming.glyphEnd / pixmap.getHeight();
|
||||
}
|
||||
}
|
||||
|
||||
return new BitmapFont(data, region, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the vertical timing line from the pixmap.
|
||||
* <p>
|
||||
* The timing line is a 1 pixel wide line on the left side of the pixmap that indicate the positions of the
|
||||
* horizontal timing lines, and the heights of the glyphs.
|
||||
* <p>
|
||||
* A white pixel on the timing line indicates the line is unused, a black indicates either a timing line, or the
|
||||
* height of a glyph.
|
||||
* <p>
|
||||
* It is expected that the line ends with a white pixel.
|
||||
*
|
||||
* @param pixmap The pixmap containing the timing line.
|
||||
* @param glyphCountY The number of glyphs in the Y direction.
|
||||
* @return An array of glyph heights.
|
||||
*/
|
||||
private VerticalTiming[] readGlyphHeights(Pixmap pixmap, int glyphCountY) {
|
||||
log.info("Reading vertical timings for {} glyphs", glyphCountY);
|
||||
|
||||
enum State {
|
||||
SEARCHING_FOR_LINE,
|
||||
WAITING_FOR_GLYPH_HEIGHTS,
|
||||
READING_GLYPH_HEIGHTS
|
||||
}
|
||||
State state = State.SEARCHING_FOR_LINE;
|
||||
|
||||
VerticalTiming[] glyphHeights = new VerticalTiming[glyphCountY];
|
||||
VerticalTiming currentTiming = null;
|
||||
int currentGlyphLine = 0;
|
||||
int y = 0;
|
||||
while (y < pixmap.getHeight()) {
|
||||
boolean isBlack = pixmap.getPixel(0, y) == 0x000000ff;
|
||||
switch (state) {
|
||||
case SEARCHING_FOR_LINE -> {
|
||||
if (isBlack) {
|
||||
currentTiming = new VerticalTiming();
|
||||
glyphHeights[currentGlyphLine++] = currentTiming;
|
||||
currentTiming.timingLine = y;
|
||||
state = State.WAITING_FOR_GLYPH_HEIGHTS;
|
||||
}
|
||||
}
|
||||
case WAITING_FOR_GLYPH_HEIGHTS -> {
|
||||
if (isBlack) {
|
||||
currentTiming.glyphStart = y;
|
||||
state = State.READING_GLYPH_HEIGHTS;
|
||||
}
|
||||
}
|
||||
case READING_GLYPH_HEIGHTS -> {
|
||||
if (!isBlack) {
|
||||
currentTiming.glyphEnd = y;
|
||||
state = State.SEARCHING_FOR_LINE;
|
||||
log.info("Found glyph height: {} (from {} to {})", currentTiming.getGlyphHeight(), currentTiming.glyphStart, currentTiming.glyphEnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
y++;
|
||||
}
|
||||
|
||||
return glyphHeights;
|
||||
}
|
||||
|
||||
private static class VerticalTiming {
|
||||
/// The Y position of the horizontal timing line.
|
||||
int timingLine;
|
||||
/// The start Y position of the glyph line.
|
||||
int glyphStart;
|
||||
/// The end Y position of the glyph line.
|
||||
int glyphEnd;
|
||||
|
||||
public int getGlyphHeight() {
|
||||
return glyphEnd - glyphStart;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the horizontal timing lines from the pixmap.
|
||||
* <p>
|
||||
* The timing lines are 1 pixel high lines and can occur at any Y position in the pixmap.
|
||||
* The Y position is indicated through the vertical timing.
|
||||
* <p>
|
||||
* A white pixel on the timing line indicates the line is unused, a line of black pixels indicates the width of a glyph.
|
||||
* @param pixmap The pixmap containing the timing lines.
|
||||
* @param glyphCountX The number of glyphs in the X direction.
|
||||
* @param timingLineY The Y position of the timing line to read.
|
||||
* @return An array of glyph widths.
|
||||
*/
|
||||
private HorizontalTiming[] readHorizontalTimings(Pixmap pixmap, int glyphCountX, int timingLineY) {
|
||||
log.info("Reading horizontal timings for {} glyphs at y={}", glyphCountX, timingLineY);
|
||||
|
||||
enum State {
|
||||
WAITING_FOR_GLYPH_WIDTHS,
|
||||
READING_GLYPH_WIDTHS
|
||||
}
|
||||
State state = State.WAITING_FOR_GLYPH_WIDTHS;
|
||||
|
||||
HorizontalTiming[] glyphWidths = new HorizontalTiming[glyphCountX];
|
||||
HorizontalTiming currentTiming = null;
|
||||
int currentGlyphIndex = 0;
|
||||
int x = 1;
|
||||
while (x < pixmap.getWidth()) {
|
||||
boolean isBlack = pixmap.getPixel(x, timingLineY) == 0x000000ff;
|
||||
switch (state) {
|
||||
case WAITING_FOR_GLYPH_WIDTHS -> {
|
||||
if (isBlack) {
|
||||
currentTiming = new HorizontalTiming();
|
||||
glyphWidths[currentGlyphIndex++] = currentTiming;
|
||||
currentTiming.glyphStart = x;
|
||||
state = State.READING_GLYPH_WIDTHS;
|
||||
}
|
||||
}
|
||||
case READING_GLYPH_WIDTHS -> {
|
||||
if (!isBlack) {
|
||||
currentTiming.glyphEnd = x;
|
||||
state = State.WAITING_FOR_GLYPH_WIDTHS;
|
||||
log.info("Found glyph width: {} (from {} to {})", currentTiming.getGlyphWidth(), currentTiming.glyphStart, currentTiming.glyphEnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
x++;
|
||||
}
|
||||
|
||||
return glyphWidths;
|
||||
}
|
||||
|
||||
private static class HorizontalTiming {
|
||||
/// The X position of the vertical timing line.
|
||||
int glyphStart;
|
||||
/// The end X position of the vertical timing line.
|
||||
int glyphEnd;
|
||||
|
||||
public int getGlyphWidth() {
|
||||
return glyphEnd - glyphStart;
|
||||
}
|
||||
var metadataString = Gdx.files.internal(path + ".toml").readString();
|
||||
var metadata = mapper.readValue(metadataString, FontMetadata.class);
|
||||
@Cleanup("dispose") var pixmap = loadPixmap(metadata.getGeneral().getSource());
|
||||
return FontLoader.loadBitmapfont(pixmap, metadata);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,7 @@ public class WindowService implements OnStartup {
|
||||
|
||||
@Override
|
||||
public void onStartup() {
|
||||
var texture = resourceLoader.loadTexture("system/menubar.png");
|
||||
menubar = new NinePatch(texture, 9, 9, 15, 1);
|
||||
menubar = resourceLoader.loadNinePatch("system/menubar.png");
|
||||
|
||||
systemMenubar = new Menubar()
|
||||
.addItem("Dice")
|
||||
@@ -25,6 +24,6 @@ public class WindowService implements OnStartup {
|
||||
|
||||
void paint(GraphicsContext gc) {
|
||||
// Render menubar
|
||||
menubar.draw(gc.getBatch(), 0, gc.getHeight() - 16, gc.getWidth(), 16);
|
||||
gc.draw(menubar, 0, 0, gc.getWidth(), 13);
|
||||
}
|
||||
}
|
||||
|
||||
215
src/main/java/be/seeseemelk/diceos/system/font/FontLoader.java
Normal file
215
src/main/java/be/seeseemelk/diceos/system/font/FontLoader.java
Normal file
@@ -0,0 +1,215 @@
|
||||
package be.seeseemelk.diceos.system.font;
|
||||
|
||||
import be.seeseemelk.diceos.system.utils.Map2D;
|
||||
import com.badlogic.gdx.graphics.Pixmap;
|
||||
import com.badlogic.gdx.graphics.Texture;
|
||||
import com.badlogic.gdx.graphics.g2d.BitmapFont;
|
||||
import com.badlogic.gdx.graphics.g2d.TextureRegion;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Loads bitmap fonts from a TOML file and a PNG file containing timing information.
|
||||
* <p>
|
||||
* This class should not be used directly. Instead, use the {@link be.seeseemelk.diceos.system.ResourceLoader}.
|
||||
*/
|
||||
@UtilityClass
|
||||
@Slf4j
|
||||
public class FontLoader {
|
||||
/**
|
||||
* Loads a bitmap font from a pixmap and metadata.
|
||||
* @param pixmap The pixmap containing the font texture and timing information.
|
||||
* @param metadata The metadata in TOML format.
|
||||
* @return The loaded bitmap font.
|
||||
*/
|
||||
public static BitmapFont loadBitmapfont(Pixmap pixmap, FontMetadata metadata) {
|
||||
var texture = new Texture(pixmap);
|
||||
var region = new TextureRegion(texture);
|
||||
|
||||
var data = new BitmapFont.BitmapFontData();
|
||||
|
||||
var glyphMap = new Map2D<Integer, Integer, BitmapFont.Glyph>();
|
||||
var glyphLines = metadata.getGeneral().getOrder().split("\n");
|
||||
for (var y = 0; y < glyphLines.length; y++) {
|
||||
var line = glyphLines[y].toCharArray();
|
||||
for (var x = 0; x < line.length; x++) {
|
||||
var glyph = new BitmapFont.Glyph();
|
||||
glyph.id = line[x];
|
||||
glyphMap.put(x, y, glyph);
|
||||
data.setGlyph(glyph.id, glyph);
|
||||
}
|
||||
}
|
||||
|
||||
// Read the vertical timing lines to find the horizontal ones and read glyph heights
|
||||
VerticalTiming[] glyphHeights = readGlyphHeights(pixmap, glyphLines.length);
|
||||
// Read the horizontal timing lines to read glyph widths
|
||||
HorizontalTiming[][] glyphWidths = new HorizontalTiming[glyphLines.length][];
|
||||
for (var y = 0; y < glyphHeights.length; y++) {
|
||||
glyphWidths[y] = readHorizontalTimings(pixmap, glyphLines[y].length(), glyphHeights[y].timingLine);
|
||||
}
|
||||
|
||||
// Assign glyph sizes
|
||||
for (var y = 0; y < glyphLines.length; y++) {
|
||||
for (var x = 0; x < glyphLines[y].length(); x++) {
|
||||
var glyph = glyphMap.get(x, y);
|
||||
var widthTiming = glyphWidths[y][x];
|
||||
var heightTiming = glyphHeights[y];
|
||||
glyph.width = widthTiming.getGlyphWidth();
|
||||
glyph.height = heightTiming.getGlyphHeight();
|
||||
glyph.srcX = widthTiming.glyphStart;
|
||||
glyph.srcY = heightTiming.glyphStart;
|
||||
glyph.xoffset = 0;
|
||||
glyph.yoffset = 0;
|
||||
glyph.xadvance = glyph.width + metadata.getPadding().getHorizontal();
|
||||
glyph.u = (float)widthTiming.glyphStart / pixmap.getWidth();
|
||||
glyph.u2 = (float)widthTiming.glyphEnd / pixmap.getWidth();
|
||||
glyph.v = (float)heightTiming.glyphStart / pixmap.getHeight();
|
||||
glyph.v2 = (float)heightTiming.glyphEnd / pixmap.getHeight();
|
||||
}
|
||||
}
|
||||
|
||||
// Add space character
|
||||
var spaceGlyph = new BitmapFont.Glyph();
|
||||
spaceGlyph.id = ' ';
|
||||
spaceGlyph.width = 0;
|
||||
spaceGlyph.height = 0;
|
||||
spaceGlyph.xadvance = metadata.getPadding().getSpace();
|
||||
data.setGlyph(spaceGlyph.id, spaceGlyph);
|
||||
|
||||
return new BitmapFont(data, region, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the vertical timing line from the pixmap.
|
||||
* <p>
|
||||
* The timing line is a 1 pixel wide line on the left side of the pixmap that indicate the positions of the
|
||||
* horizontal timing lines, and the heights of the glyphs.
|
||||
* <p>
|
||||
* A white pixel on the timing line indicates the line is unused, a black indicates either a timing line, or the
|
||||
* height of a glyph.
|
||||
* <p>
|
||||
* It is expected that the line ends with a white pixel.
|
||||
*
|
||||
* @param pixmap The pixmap containing the timing line.
|
||||
* @param glyphCountY The number of glyphs in the Y direction.
|
||||
* @return An array of glyph heights.
|
||||
*/
|
||||
private static VerticalTiming[] readGlyphHeights(Pixmap pixmap, int glyphCountY) {
|
||||
log.info("Reading vertical timings for {} glyphs", glyphCountY);
|
||||
|
||||
enum State {
|
||||
SEARCHING_FOR_LINE,
|
||||
WAITING_FOR_GLYPH_HEIGHTS,
|
||||
READING_GLYPH_HEIGHTS
|
||||
}
|
||||
State state = State.SEARCHING_FOR_LINE;
|
||||
|
||||
VerticalTiming[] glyphHeights = new VerticalTiming[glyphCountY];
|
||||
VerticalTiming currentTiming = null;
|
||||
int currentGlyphLine = 0;
|
||||
int y = 0;
|
||||
while (y < pixmap.getHeight()) {
|
||||
boolean isBlack = pixmap.getPixel(0, y) == 0x000000ff;
|
||||
switch (state) {
|
||||
case SEARCHING_FOR_LINE -> {
|
||||
if (isBlack) {
|
||||
currentTiming = new VerticalTiming();
|
||||
glyphHeights[currentGlyphLine++] = currentTiming;
|
||||
currentTiming.timingLine = y;
|
||||
state = State.WAITING_FOR_GLYPH_HEIGHTS;
|
||||
}
|
||||
}
|
||||
case WAITING_FOR_GLYPH_HEIGHTS -> {
|
||||
if (isBlack) {
|
||||
currentTiming.glyphStart = y;
|
||||
state = State.READING_GLYPH_HEIGHTS;
|
||||
}
|
||||
}
|
||||
case READING_GLYPH_HEIGHTS -> {
|
||||
if (!isBlack) {
|
||||
currentTiming.glyphEnd = y;
|
||||
state = State.SEARCHING_FOR_LINE;
|
||||
log.info("Found glyph height: {} (from {} to {})", currentTiming.getGlyphHeight(), currentTiming.glyphStart, currentTiming.glyphEnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
y++;
|
||||
}
|
||||
|
||||
return glyphHeights;
|
||||
}
|
||||
|
||||
private static class VerticalTiming {
|
||||
/// The Y position of the horizontal timing line.
|
||||
int timingLine;
|
||||
/// The start Y position of the glyph line.
|
||||
int glyphStart;
|
||||
/// The end Y position of the glyph line.
|
||||
int glyphEnd;
|
||||
|
||||
public int getGlyphHeight() {
|
||||
return glyphEnd - glyphStart;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the horizontal timing lines from the pixmap.
|
||||
* <p>
|
||||
* The timing lines are 1 pixel high lines and can occur at any Y position in the pixmap.
|
||||
* The Y position is indicated through the vertical timing.
|
||||
* <p>
|
||||
* A white pixel on the timing line indicates the line is unused, a line of black pixels indicates the width of a glyph.
|
||||
* @param pixmap The pixmap containing the timing lines.
|
||||
* @param glyphCountX The number of glyphs in the X direction.
|
||||
* @param timingLineY The Y position of the timing line to read.
|
||||
* @return An array of glyph widths.
|
||||
*/
|
||||
private static HorizontalTiming[] readHorizontalTimings(Pixmap pixmap, int glyphCountX, int timingLineY) {
|
||||
log.info("Reading horizontal timings for {} glyphs at y={}", glyphCountX, timingLineY);
|
||||
|
||||
enum State {
|
||||
WAITING_FOR_GLYPH_WIDTHS,
|
||||
READING_GLYPH_WIDTHS
|
||||
}
|
||||
State state = State.WAITING_FOR_GLYPH_WIDTHS;
|
||||
|
||||
HorizontalTiming[] glyphWidths = new HorizontalTiming[glyphCountX];
|
||||
HorizontalTiming currentTiming = null;
|
||||
int currentGlyphIndex = 0;
|
||||
int x = 1;
|
||||
while (x < pixmap.getWidth()) {
|
||||
boolean isBlack = pixmap.getPixel(x, timingLineY) == 0x000000ff;
|
||||
switch (state) {
|
||||
case WAITING_FOR_GLYPH_WIDTHS -> {
|
||||
if (isBlack) {
|
||||
currentTiming = new HorizontalTiming();
|
||||
glyphWidths[currentGlyphIndex++] = currentTiming;
|
||||
currentTiming.glyphStart = x;
|
||||
state = State.READING_GLYPH_WIDTHS;
|
||||
}
|
||||
}
|
||||
case READING_GLYPH_WIDTHS -> {
|
||||
if (!isBlack) {
|
||||
currentTiming.glyphEnd = x;
|
||||
state = State.WAITING_FOR_GLYPH_WIDTHS;
|
||||
log.info("Found glyph width: {} (from {} to {})", currentTiming.getGlyphWidth(), currentTiming.glyphStart, currentTiming.glyphEnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
x++;
|
||||
}
|
||||
|
||||
return glyphWidths;
|
||||
}
|
||||
|
||||
private static class HorizontalTiming {
|
||||
/// The X position of the vertical timing line.
|
||||
int glyphStart;
|
||||
/// The end X position of the vertical timing line.
|
||||
int glyphEnd;
|
||||
|
||||
public int getGlyphWidth() {
|
||||
return glyphEnd - glyphStart;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package be.seeseemelk.diceos.system.font;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class FontMetadata {
|
||||
private GeneralSection general;
|
||||
private PaddingSection padding;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package be.seeseemelk.diceos.system.font;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class GeneralSection {
|
||||
/**
|
||||
* The image file containing the font glyphs.
|
||||
*/
|
||||
private String source;
|
||||
|
||||
/**
|
||||
* The order of the glyphs in the image file.
|
||||
*/
|
||||
private String order;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package be.seeseemelk.diceos.system.font;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class PaddingSection {
|
||||
/**
|
||||
* The number of pixels to pad between characters.
|
||||
*/
|
||||
private int horizontal;
|
||||
|
||||
/**
|
||||
* The width of the space character.
|
||||
*/
|
||||
private int space;
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package be.seeseemelk.diceos.system.gfx;
|
||||
|
||||
import com.badlogic.gdx.graphics.Texture;
|
||||
import com.badlogic.gdx.graphics.g2d.Batch;
|
||||
import com.badlogic.gdx.graphics.g2d.NinePatch;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@@ -13,4 +15,26 @@ public class GraphicsContext {
|
||||
private final Batch batch;
|
||||
private final int height;
|
||||
private final int width;
|
||||
|
||||
public void draw(NinePatch image, int x, int y, int width, int height) {
|
||||
image.draw(batch, x, getHeight() - height - y, width, height);
|
||||
}
|
||||
|
||||
public void draw(Texture texture, int x, int y) {
|
||||
draw(texture, x, y, Parameters.DEFAULT);
|
||||
}
|
||||
|
||||
public void draw(Texture texture, int x, int y, Parameters params) {
|
||||
batch.draw(texture,
|
||||
x, getHeight() - y - texture.getHeight(), texture.getWidth(), texture.getHeight(),
|
||||
0, 0, texture.getWidth(), texture.getHeight(),
|
||||
params.flipX, params.flipY);
|
||||
}
|
||||
|
||||
public static class Parameters {
|
||||
public final static Parameters DEFAULT = new Parameters();
|
||||
|
||||
public boolean flipX = false;
|
||||
public boolean flipY = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package be.seeseemelk.diceos.system.gfx;
|
||||
|
||||
import com.badlogic.gdx.graphics.Pixmap;
|
||||
import com.badlogic.gdx.graphics.Texture;
|
||||
import com.badlogic.gdx.graphics.g2d.NinePatch;
|
||||
import com.badlogic.gdx.graphics.g2d.TextureRegion;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* Loads a 9-patch image from a pixmap.
|
||||
*/
|
||||
@Getter
|
||||
public class NinePatchLoader {
|
||||
private final Pixmap pixmap;
|
||||
private int xStart = 0;
|
||||
private int xEnd = 0;
|
||||
private int yStart = 0;
|
||||
private int yEnd = 0;
|
||||
private NinePatch ninePatch;
|
||||
|
||||
public NinePatchLoader(Pixmap pixmap) {
|
||||
this.pixmap = pixmap;
|
||||
parse();
|
||||
}
|
||||
|
||||
private void parse() {
|
||||
// Top border
|
||||
for (int x = 1; x < pixmap.getWidth() - 1; x++) {
|
||||
int pixel = pixmap.getPixel(x, 0);
|
||||
var isBlack = pixel == 0x000000ff;
|
||||
if (isBlack) {
|
||||
if (xStart == 0) {
|
||||
xStart = x - 1;
|
||||
}
|
||||
xEnd = x - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Left border
|
||||
for (int y = 1; y < pixmap.getHeight() - 1; y++) {
|
||||
int pixel = pixmap.getPixel(0, y);
|
||||
var isBlack = pixel == 0x000000ff;
|
||||
if (isBlack) {
|
||||
if (yStart == 0) {
|
||||
yStart = y - 1;
|
||||
}
|
||||
yEnd = y - 1;
|
||||
}
|
||||
}
|
||||
|
||||
var texture = new Texture(pixmap);
|
||||
var region = new TextureRegion(texture,
|
||||
//1, 1,
|
||||
1, 1,
|
||||
pixmap.getWidth() - 1, pixmap.getHeight() - 1
|
||||
);
|
||||
ninePatch = new NinePatch(region,
|
||||
xStart, pixmap.getWidth() - xEnd - 2,
|
||||
yStart, pixmap.getHeight() - yEnd - 2
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
info face="Dice Font" size=8 bold=0 italic=0 charset="" unicode=0 stretchH=100 smooth=0 aa=0 padding=1,1,1,1 spacing=1,1
|
||||
common lineHeight=8 base=7 scaleW=160 scaleH=28 pages=1 packed=0 alphaChnl=0
|
||||
page id=0 file="font.png"
|
||||
char id=65 x=5 y=0 width=60 height=7 xoffset=5 yoffset=5 xadvance=6 page=0 chnl=15
|
||||
10
src/main/resources/system/font.toml
Normal file
10
src/main/resources/system/font.toml
Normal file
@@ -0,0 +1,10 @@
|
||||
[general]
|
||||
source = "system/font.png"
|
||||
order = """
|
||||
aAbBcCdDeEfFgGhHiIjJkKlLmMnN
|
||||
oOpPqQrRsStTuUvVwWxXyYzZ
|
||||
"""
|
||||
|
||||
[padding]
|
||||
horizontal = 1
|
||||
space = 2
|
||||
@@ -1,2 +0,0 @@
|
||||
aAbBcCdDeEfFgGhHiIjJkKlLmMnN
|
||||
oOpPqQrRsStTuUvVwWxXyYzZ
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 305 B After Width: | Height: | Size: 257 B |
Reference in New Issue
Block a user