Add TOML font loading support and refactor font handling

This commit is contained in:
2025-12-19 12:41:11 +01:00
parent 20f9a2107d
commit 1ef39458ce
15 changed files with 390 additions and 219 deletions

View File

@@ -34,6 +34,9 @@ dependencies {
testImplementation("io.avaje:avaje-inject-test:${avajeInjectVersion}") testImplementation("io.avaje:avaje-inject-test:${avajeInjectVersion}")
testAnnotationProcessor("io.avaje:avaje-inject-generator:${avajeInjectVersion}") testAnnotationProcessor("io.avaje:avaje-inject-generator:${avajeInjectVersion}")
// Parsing TOML files
implementation("tools.jackson.dataformat:jackson-dataformat-toml:3.0.2")
// LibGDX // LibGDX
api("com.badlogicgames.gdx:gdx:$gdxVersion") api("com.badlogicgames.gdx:gdx:$gdxVersion")
api("com.badlogicgames.gdx:gdx-backend-lwjgl3:$gdxVersion") api("com.badlogicgames.gdx:gdx-backend-lwjgl3:$gdxVersion")

View File

@@ -45,7 +45,6 @@ public class DiceOS extends ApplicationAdapter {
vmViewport = new ScreenViewport(); vmViewport = new ScreenViewport();
vmViewport.update(display.getWidth(), display.getHeight(), true); vmViewport.update(display.getWidth(), display.getHeight(), true);
for (var task : startupTasks) { for (var task : startupTasks) {
task.onStartup(); task.onStartup();
} }
@@ -83,22 +82,21 @@ public class DiceOS extends ApplicationAdapter {
ScreenUtils.clear(Color.GREEN); ScreenUtils.clear(Color.GREEN);
// Render background // Render background
display.draw(clouds, 0, 0); gc.draw(clouds, 0, 0);
windowService.paint(gc); windowService.paint(gc);
// Render borders // Render borders
var param = new DisplayService.Parameters(); var param = new GraphicsContext.Parameters();
display.draw(border, 0, 0, param); gc.draw(border, 0, 0, param);
param.flipX = true; param.flipX = true;
display.draw(border, display.getWidth() - 8, 0, param); gc.draw(border, display.getWidth() - 8, 0, param);
param.flipY = true; 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; 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(), "This is DiceOS", 6, display.getHeight() - 12);
font.draw(display.getBatch(), "DiceOS", 50, display.getHeight() - 40);
// Finish rendering to screen buffer // Finish rendering to screen buffer
display.getBatch().end(); display.getBatch().end();

View File

@@ -41,22 +41,4 @@ public class DisplayService implements OnStartup {
public int getHeight() { public int getHeight() {
return 360; 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;
}
} }

View File

@@ -1,15 +1,19 @@
package be.seeseemelk.diceos.system; 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.Gdx;
import com.badlogic.gdx.graphics.Pixmap; import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.BitmapFont; 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 io.avaje.inject.Component;
import lombok.Cleanup; import lombok.Cleanup;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.dataformat.toml.TomlFactory;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -20,7 +24,9 @@ import java.util.Map;
public class ResourceLoader { public class ResourceLoader {
private final Map<String, Pixmap> pixmaps = new HashMap<>(); private final Map<String, Pixmap> pixmaps = new HashMap<>();
private final Map<String, Texture> textures = 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 Map<String, BitmapFont> fonts = new HashMap<>();
private final ObjectMapper mapper = new ObjectMapper(new TomlFactory());
public Pixmap loadPixmap(String path) { public Pixmap loadPixmap(String path) {
return pixmaps.computeIfAbsent(path, p -> new Pixmap(Gdx.files.internal(p))); 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))); 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) { public BitmapFont loadFont(String path) {
return fonts.computeIfAbsent(path, this::loadBitmapfont); return fonts.computeIfAbsent(path, this::loadBitmapfont);
} }
@@ -37,186 +51,9 @@ public class ResourceLoader {
private BitmapFont loadBitmapfont(String path) { private BitmapFont loadBitmapfont(String path) {
log.info("Loading bitmap font from {}", path); log.info("Loading bitmap font from {}", path);
@Cleanup("dispose") var pixmap = loadPixmap(path + ".png"); var metadataString = Gdx.files.internal(path + ".toml").readString();
var texture = new Texture(pixmap); var metadata = mapper.readValue(metadataString, FontMetadata.class);
var region = new TextureRegion(texture); @Cleanup("dispose") var pixmap = loadPixmap(metadata.getGeneral().getSource());
return FontLoader.loadBitmapfont(pixmap, metadata);
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;
}
} }
} }

View File

@@ -15,8 +15,7 @@ public class WindowService implements OnStartup {
@Override @Override
public void onStartup() { public void onStartup() {
var texture = resourceLoader.loadTexture("system/menubar.png"); menubar = resourceLoader.loadNinePatch("system/menubar.png");
menubar = new NinePatch(texture, 9, 9, 15, 1);
systemMenubar = new Menubar() systemMenubar = new Menubar()
.addItem("Dice") .addItem("Dice")
@@ -25,6 +24,6 @@ public class WindowService implements OnStartup {
void paint(GraphicsContext gc) { void paint(GraphicsContext gc) {
// Render menubar // Render menubar
menubar.draw(gc.getBatch(), 0, gc.getHeight() - 16, gc.getWidth(), 16); gc.draw(menubar, 0, 0, gc.getWidth(), 13);
} }
} }

View 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;
}
}
}

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,8 @@
package be.seeseemelk.diceos.system.gfx; 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.Batch;
import com.badlogic.gdx.graphics.g2d.NinePatch;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@@ -13,4 +15,26 @@ public class GraphicsContext {
private final Batch batch; private final Batch batch;
private final int height; private final int height;
private final int width; 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;
}
} }

View File

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

View File

@@ -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

View File

@@ -0,0 +1,10 @@
[general]
source = "system/font.png"
order = """
aAbBcCdDeEfFgGhHiIjJkKlLmMnN
oOpPqQrRsStTuUvVwWxXyYzZ
"""
[padding]
horizontal = 1
space = 2

View File

@@ -1,2 +0,0 @@
aAbBcCdDeEfFgGhHiIjJkKlLmMnN
oOpPqQrRsStTuUvVwWxXyYzZ

Binary file not shown.

Before

Width:  |  Height:  |  Size: 305 B

After

Width:  |  Height:  |  Size: 257 B