Improve .hex font loading code.

- Optimize string logic to reduce unnecessary object churn (~2x faster font load times).
- Add support for multiple font.hex IResources - this allows resource packs to only partially
  override the OpenComputers font without removing existing glyphs, opening the door for
  non-compatibility-breaking glyph addition packs or partial alternate fonts.
This commit is contained in:
Adrian Siekierka 2022-11-11 10:13:02 +01:00
parent 707ffc57a5
commit dc55abe1c4
2 changed files with 75 additions and 47 deletions

View File

@ -6,6 +6,7 @@ import li.cil.oc.OpenComputers;
import li.cil.oc.Settings;
import li.cil.oc.util.FontUtils;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.IResource;
import net.minecraft.util.ResourceLocation;
import org.lwjgl.BufferUtils;
@ -14,6 +15,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.util.List;
public class FontParserHex implements IGlyphProvider {
private static final byte[] OPAQUE = {(byte) 255, (byte) 255, (byte) 255, (byte) 255};
@ -21,47 +23,68 @@ public class FontParserHex implements IGlyphProvider {
private final TIntObjectMap<byte[]> glyphs = new TIntObjectHashMap<>();
private static int hex2int(char c) {
if (c >= '0' && c <= '9') {
return c - '0';
} else if (c >= 'A' && c <= 'F') {
return c - ('A' - 10);
} else if (c >= 'a' && c <= 'f') {
return c - ('a' - 10);
} else {
throw new RuntimeException("invalid char: " + c);
}
}
@Override
public void initialize() {
try {
final InputStream font = Minecraft.getMinecraft().getResourceManager().getResource(new ResourceLocation(Settings.resourceDomain(), "font.hex")).getInputStream();
try {
OpenComputers.log().info("Initializing unicode glyph provider.");
final BufferedReader input = new BufferedReader(new InputStreamReader(font));
String line;
int glyphCount = 0;
while ((line = input.readLine()) != null) {
final String[] info = line.split(":");
final int charCode = Integer.parseInt(info[0], 16);
if (charCode < 0 || charCode >= FontUtils.codepoint_limit()) {
OpenComputers.log().warn(String.format("Unicode font contained unexpected glyph: U+%04X, ignoring", charCode));
continue; // Out of bounds.
}
final int expectedWidth = FontUtils.wcwidth(charCode);
if (expectedWidth < 1) continue; // Skip control characters.
// Two chars representing one byte represent one row of eight pixels.
final byte[] glyph = new byte[info[1].length() >> 1];
final int glyphWidth = glyph.length / getGlyphHeight();
if (expectedWidth == glyphWidth) {
for (int i = 0; i < glyph.length; i++) {
glyph[i] = (byte) Integer.parseInt(info[1].substring(i * 2, i * 2 + 2), 16);
}
if (!glyphs.containsKey(charCode)) {
glyphCount++;
}
glyphs.put(charCode, glyph);
} else if (Settings.get().logHexFontErrors()) {
OpenComputers.log().warn(String.format("Size of glyph for code point U+%04X (%s) in font (%d) does not match expected width (%d), ignoring.", charCode, String.valueOf((char) charCode), glyphWidth, expectedWidth));
}
}
OpenComputers.log().info("Loaded " + glyphCount + " glyphs.");
} finally {
glyphs.clear();
OpenComputers.log().info("Loading Unicode glyphs...");
long time = System.currentTimeMillis();
int glyphCount = 0;
ResourceLocation loc = new ResourceLocation(Settings.resourceDomain(), "font.hex");
for (IResource resource : (List<IResource>) Minecraft.getMinecraft().getResourceManager().getAllResources(loc)) {
final InputStream font = resource.getInputStream();
try {
font.close();
} catch (IOException ex) {
OpenComputers.log().warn("Error parsing font.", ex);
final BufferedReader input = new BufferedReader(new InputStreamReader(font));
String line;
while ((line = input.readLine()) != null) {
final String info = line.substring(0, line.indexOf(':'));
final int charCode = Integer.parseInt(info, 16);
if (charCode < 0 || charCode >= FontUtils.codepoint_limit()) {
OpenComputers.log().warn(String.format("Unicode font contained unexpected glyph: U+%04X, ignoring", charCode));
continue; // Out of bounds.
}
final int expectedWidth = FontUtils.wcwidth(charCode);
if (expectedWidth < 1) continue; // Skip control characters.
// Two chars representing one byte represent one row of eight pixels.
int glyphStrOfs = info.length() + 1;
final byte[] glyph = new byte[(line.length() - glyphStrOfs) >> 1];
final int glyphWidth = glyph.length / getGlyphHeight();
if (expectedWidth == glyphWidth) {
for (int i = 0; i < glyph.length; i++, glyphStrOfs += 2) {
glyph[i] = (byte) ((hex2int(line.charAt(glyphStrOfs)) << 4) | (hex2int(line.charAt(glyphStrOfs + 1))));
}
if (!glyphs.containsKey(charCode)) {
glyphCount++;
}
glyphs.put(charCode, glyph);
} else if (Settings.get().logHexFontErrors()) {
OpenComputers.log().warn(String.format("Size of glyph for code point U+%04X (%s) in font (%d) does not match expected width (%d), ignoring.", charCode, String.valueOf((char) charCode), glyphWidth, expectedWidth));
}
}
} finally {
try {
font.close();
} catch (IOException ex) {
OpenComputers.log().warn("Error parsing font.", ex);
}
}
}
OpenComputers.log().info("Loaded " + glyphCount + " glyphs in " + (System.currentTimeMillis() - time) + " milliseconds.");
} catch (IOException ex) {
OpenComputers.log().warn("Failed loading glyphs.", ex);
}
@ -72,7 +95,7 @@ public class FontParserHex implements IGlyphProvider {
if (!glyphs.containsKey(charCode))
return null;
final byte[] glyph = glyphs.get(charCode);
if (glyph == null || glyph.length <= 0)
if (glyph == null || glyph.length == 0)
return null;
final ByteBuffer buffer = BufferUtils.createByteBuffer(glyph.length * getGlyphWidth() * 4);
for (byte aGlyph : glyph) {

View File

@ -138,23 +138,28 @@ object FontUtils {
else if ((charCode == 0xe0001) || ((charCode - 0xe0020) < 0x5f) || ((charCode - 0xe0100) < 0xef)) 0
else 1
}
OpenComputers.log.info("Initializing unicode wcwidth.")
for (i <- 0 until codepoint_limit) {
if (c_wcwidth(i) == 2)
defined_double_wide += i
{
OpenComputers.log.info("Initializing font glyph width cache...")
val time = System.currentTimeMillis()
for (i <- 0 until codepoint_limit) {
if (c_wcwidth(i) == 2)
defined_double_wide += i
}
OpenComputers.log.info("Initialized font glyph width cache in " + (System.currentTimeMillis() - time) + " milliseconds.")
}
try {
OpenComputers.log.info("Initializing font glyph widths.")
OpenComputers.log.info("Initializing font glyph width overrides...")
val time = System.currentTimeMillis()
val font = FontUtils.getClass.getResourceAsStream("/assets/opencomputers/font.hex")
try {
var line: String = null
val input = new BufferedReader(new InputStreamReader(font, StandardCharsets.UTF_8))
var out_of_range_glyph: Int = 0
while ({line = input.readLine; line != null}) {
val info = line.split(":")
val charCode = Integer.parseInt(info(0), 16)
val info = line.substring(0, line.indexOf(':'))
val charCode = Integer.parseInt(info, 16)
if (charCode >= 0 && charCode < codepoint_limit) {
info(1).trim.length match {
line.length - info.length - 1 match {
case 64 => defined_double_wide += charCode
case 32 => defined_double_wide -= charCode
case n => OpenComputers.log.warn(s"Invalid glyph size detected in font.hex. Expected 64 or 32, got: $n")
@ -163,8 +168,8 @@ object FontUtils {
out_of_range_glyph += 1
}
}
if (out_of_range_glyph > 1) {
OpenComputers.log.info(f"${out_of_range_glyph} total non-BMP glyph char codes detected in font.hex")
if (out_of_range_glyph >= 1) {
OpenComputers.log.info(f"${out_of_range_glyph} total out-of-bounds glyph char codes detected in font.hex")
}
} finally {
try {
@ -173,9 +178,9 @@ object FontUtils {
case ex: Throwable => OpenComputers.log.error(s"Error closing font.hex: $ex")
}
}
OpenComputers.log.info("Initialized font glyph width overrides in " + (System.currentTimeMillis() - time) + " milliseconds.")
} catch {
case ex: Throwable => OpenComputers.log.error(s"Error parsing glyphs to determine widths: $ex")
}
OpenComputers.log.info("glyph width ready.")
}
}