
net.freeutils.scrollphat.LEDFont Maven / Gradle / Ivy
The newest version!
/*
* Copyright © 2016 Amichai Rothman
*
* This file is part of JScrollPhat - the Java Scroll pHAT package.
*
* JScrollPhat is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* JScrollPhat is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with JScrollPhat. If not, see .
*
* For additional info see http://www.freeutils.net/source/jscrollphat/
*/
package net.freeutils.scrollphat;
import java.io.*;
import java.util.*;
/**
* Loads a font in ledfont format and renders text using the font.
*
* The ledfont format and this font implementation are lightweight
* and quite efficient for tiny fixed-size fonts, such as the
* ones that would be used in an embedded device or a LED matrix.
*
* Current limitations:
*
* - Height is limited to 8 pixels (1 byte)
* - Glyph Width is limited to 255 pixels (width stored in 1 byte)
* - Entire font data is stored in memory (although usually only ~2K)
*
*/
public class LEDFont {
public static byte[] MAGIC = { 'L', 'E', 'D', 'S' };
protected int height;
protected byte[] data;
protected int used;
protected Map lookup;
/**
* Constructs an empty LEDFont.
*
* @param height the font height
*/
protected LEDFont(int height) {
this.height = height;
this.data = new byte[1024];
this.lookup = new HashMap(256);
}
/**
* Constructs a LEDFont from a file or resource in ledfont format.
*
* @param filename the file (or resource) name
* @throws IOException if an error occurs
*/
public LEDFont(String filename) throws IOException {
try {
parse(Utils.readBytes(Utils.getInputStream(filename)));
} catch (FileNotFoundException fnfe) {
parse(Utils.readBytes(Utils.getInputStream(filename + ".ledfont")));
}
}
/**
* Constructs a LEDFont from a stream containing data in ledfont format.
*
* @param in the stream from which the ledfont data is read
* @throws IOException if an error occurs
*/
public LEDFont(InputStream in) throws IOException {
this(Utils.readBytes(in));
}
/**
* Constructs a LEDFont from a byte array containing data in ledfont format.
*
* @param data the data in ledfont format
*/
public LEDFont(byte[] data) {
parse(data);
}
/**
* Returns the font height in pixels.
*
* @return the font height in pixels
*/
public int getHeight() {
return height;
}
/**
* Returns the sorted set of Unicode code points supported by this font.
*
* @return the sorted set of Unicode code points supported by this font
*/
public Set getSupportedChars() {
return new TreeSet(lookup.keySet());
}
/**
* Returns a string of Unicode code points supported by this font.
*
* @return a string of Unicode code points supported by this font
*/
public String getSupportedCharsAsString() {
Set chars = getSupportedChars();
StringBuilder sb = new StringBuilder(chars.size());
for (int c : chars)
sb.appendCodePoint(c);
return sb.toString();
}
/**
* Parses data in the ledfont format.
*
* @param data the data in ledfont format
*/
protected void parse(byte[] data) {
// header magic
int i = 0;
for (; i < MAGIC.length; i++)
if (data.length <= i || data[i] != MAGIC[i])
throw new IllegalArgumentException("invalid header at position " + i);
// header data
int headerLen = data[i++] & 0xff;
int version = data[i++];
if (version != 1)
throw new IllegalArgumentException("invalid font format version " + version);
height = data[i++] & 0xff;
i += headerLen - 2;
// data
Map lookup = new HashMap(256);
while (i < data.length) {
if (i + 3 > data.length)
throw new IllegalArgumentException("data is corrupt at position " + i);
int c = (data[i++] & 0xff) | ((data[i++] & 0xff) << 8) | ((data[i++] & 0xff) << 16);
int width = data[i++] & 0xff;
if (i + width > data.length)
throw new IllegalArgumentException("data is corrupt at position " + (i - 1));
lookup.put(c, i - 1);
i += width;
}
this.data = data;
this.used = data.length;
this.lookup = lookup;
}
/**
* Saves this font into the given stream in the ledfont format.
*
* @param out the stream to save to
* @throws IOException if an error occurs
*/
protected void save(OutputStream out) throws IOException {
// write header
out.write(MAGIC);
out.write(2); // header length
out.write(1); // font format version
out.write(height); // font height in pixels
// write sorted codepoint data
for (Integer codePoint : getSupportedChars()) { // sorted iteration
Integer i = lookup.get(codePoint);
int width = data[i] & 0xff;
// write code point (3 bytes, since max valid unicode value is 0x10FFFF)
out.write(codePoint);
out.write(codePoint >> 8);
out.write(codePoint >> 16);
// write glyph width (the number of columns/bytes that follow)
out.write(width);
// write pixel columns
out.write(data, i + 1, width);
}
}
/**
* Saves this font into the given file in the ledfont format.
*
* @param out the file to save to
* @throws IOException if an error occurs
*/
protected void save(File out) throws IOException {
OutputStream os = null;
try {
os = new BufferedOutputStream(new FileOutputStream(out));
save(os);
} finally {
if (os != null)
os.close();
}
}
/**
* Returns the maximum glyph width among
* all of the given characters.
*
* @param chars the code points
* @return the maximum width
*/
public int getMaxWidth(char[] chars) {
int max = 0;
for (int i = 0; i < chars.length;) {
int c = Character.codePointAt(chars, i);
if (getWidth(c) > max)
max = getWidth(c);
i += Character.charCount(c);
}
return max;
}
/**
* Adds a character to this font.
*
* @param c the Unicode code point
* @param data an array containing the glyph data
* @param offset the offset within the array where the glyph data starts
* @param length the length of the glyph data (the glyph width)
*/
protected void addChar(int c, byte[] data, int offset, int length) {
// expand data array if necessary
if (used + length + 1 >= this.data.length) {
byte[] temp = new byte[this.data.length * 2];
System.arraycopy(this.data, 0, temp, 0, used);
this.data = temp;
}
lookup.put(c, used);
this.data[used++] = (byte)length;
System.arraycopy(data, offset, this.data, used, length);
used += length;
}
/**
* Returns the width in pixels of the given code point.
*
* @param c a code point
* @return the width in pixels of the given code point,
* or 0 if the code point is not supported
*/
public int getWidth(int c) {
Integer i = lookup.get(c);
return i == null ? 0 : data[i] & 0xff;
}
/**
* Returns the width in pixels of the given characters.
*
* @param chars a sequence of characters
* @param start the start index of the character sequence
* @param len the length of the character sequence
* @return the width in pixels of the given characters
*/
public int getWidth(char[] chars, int start, int len) {
int width = 0;
for (int i = start, end = start + len; i < end;) {
int c = Character.codePointAt(chars, i, end);
width += getWidth(c);
i += Character.charCount(c);
}
return width;
}
/**
* Returns the width in pixels of the given string.
*
* @param s a sequence of characters
* @return the width in pixels of the given string
*/
public int getWidth(String s) {
int width = 0;
for (int i = 0, end = s.length(); i < end;) {
int c = s.codePointAt(i);
width += getWidth(c);
i += Character.charCount(c);
}
return width;
}
/**
* Writes the glyph for the given code point into an array.
*
* @param c a code point
* @param buf an array into which the glyph is written
* @param offset the offset within the array at which to write
* @return the width of the written glyphs in pixels
*/
public int write(int c, byte[] buf, int offset) {
Integer i = lookup.get(c);
if (i == null)
return 0;
int width = data[i] & 0xff;
System.arraycopy(data, i + 1, buf, offset, width);
return width;
}
/**
* Writes the glyphs for the given characters into an array.
*
* @param chars a sequence of characters
* @param start the index of the first character to write
* @param len the length of the character sequence to write
* @param buf an array into which the glyphs are written
* @param offset the offset within the array at which to write
* @return the width of the written glyphs in pixels
*/
public int write(char[] chars, int start, int len, byte[] buf, int offset) {
int width = 0;
for (int i = start, end = start + len; i < end;) {
int c = Character.codePointAt(chars, i, end);
width += write(c, buf, offset + width);
i += Character.charCount(c);
}
return width;
}
/**
* Writes the glyphs for the given string into an array.
*
* @param s a sequence of characters
* @param buf an array into which the glyphs are written
* @param offset the offset within the array at which to write
* @return the width of the written glyphs in pixels
*/
public int write(String s, byte[] buf, int offset) {
int width = 0;
int len = s.length();
for (int i = 0; i < len;) {
int c = s.codePointAt(i);
width += write(c, buf, offset + width);
i += Character.charCount(c);
}
return width;
}
/**
* Writes the glyphs for the given string.
*
* @param s a sequence of characters
* @return an array containing the written glyphs
*/
public byte[] write(String s) {
int width = getWidth(s);
byte[] buf = new byte[width];
write(s, buf, 0);
return buf;
}
/**
* Writes the glyphs for the given sequence of characters into
* an ASCII-art string. This is very useful for testing.
*
* @param s a sequence of characters
* @param lineWidth the maximum width of the ASCII-art line
* in pixels (after which a new line will be started)
* @return the ASCII-art string of glyphs
*/
public String toAsciiArt(String s, int lineWidth) {
int len = s.length();
int width = 0;
StringBuilder sb = new StringBuilder(len);
int j = 0;
for (int i = 0; i < len;) {
int c = s.codePointAt(i);
width += getWidth(c);
if (width >= lineWidth) {
String line = s.substring(j, i);
String art = Canvas.toAsciiArt(write(line), getHeight());
sb.append(art).append('\n');
width = getWidth(c);
j = i;
}
i += Character.charCount(c);
}
String line = s.substring(j, len);
String art = Canvas.toAsciiArt(write(line), getHeight());
sb.append(art).append('\n');
return sb.toString();
}
/**
* The main command-line utility entry point.
*
* @param args the arguments
* @throws IOException if an error occurs
*/
public static void main(String[] args) throws IOException {
if (args.length == 0) {
System.out.println("Usage: LEDFont [text]");
System.exit(-1);
}
LEDFont font = new LEDFont(args[0]);
String text;
if (args.length > 1) {
text = args[1];
} else {
text = font.getSupportedCharsAsString();
System.out.println("Supported chars: [" + text + "]");
}
System.out.println(font.toAsciiArt(text, 80));
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy