All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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