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

net.freeutils.scrollphat.FontConverter 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.awt.*;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Converts a standard TrueType (TTF) font, rendered at a given size,
 * to the ledfont format used at runtime by the LEDFont class.
 * 

* In order to conserve resources on resource-limited devices (such as * the Raspberry Pi), this utility can be used manually during development * to generate font files in the lightweight format used by LEDFont, * which in turn can be used during runtime to render text using the font. *

* Note that most standard desktop fonts look terrible, and often illegible, * when rendered at tiny sizes for which they were not designed. * However, if you look around you can find a dozen or two fonts that were * designed specifically for tiny sizes and will look good even in a target * font height of e.g. 5 pixels. Some of these are categorized as pixel fonts. *

* You may need to try various point sizes, one at a time, until you find * the one that looks best for your target font height in pixels. For example, * several fonts look best when rendered at a size of 8 points and cropped * to a target height of 5 pixels (due ascent/descent spaces, etc.) *

* Please check the licensing terms of the fonts you use. Most of them are * free for personal use, and many are also free for commercial use, but * you'll need to verify compliance on a case-by-case basis. * If you create your own fonts, please consider sharing them freely as well! *

* The {@code -d} or {@code -t} command line arguments can be used to print * the converted font glyphs to the console to quickly assess how it looks. *

* The font pixel data is currently stored using one byte per column, * i.e. there is a font height limit of 8 pixels. */ public class FontConverter { /** * Loads a TrueType (TTF) font from file or resource as an AWT Font. * * @param filename the file (or resource) name * @return the font * @throws IOException if an error occurs */ public static Font loadTTF(String filename) throws IOException { InputStream in = null; try { in = Utils.getInputStream(filename); return Font.createFont(Font.TRUETYPE_FONT, in); } catch (FontFormatException ffe) { throw new IOException(ffe.toString()); } finally { if (in != null) in.close(); } } /** * Converts a single glyph's pixels into ledfont column data. * * @param out the array to write to (must be at least as large as width) * @param image an image on which the character's glyph is drawn * @param baseline the glyph's baseline (in pixels from the top) * @param width the glyph's width * @param height the glyph's height * @throws IOException if an error occurs */ static void convertPixels(byte[] out, BufferedImage image, int baseline, int width, int height) throws IOException { int imageHeight = image.getHeight(); for (int col = 0; col < width; col++) { byte b = 0; for (int row = 0; row < height; row++) { int y = baseline - height + row; if (y >= 0 && y < imageHeight) { int rgb = image.getRGB(col, y) & 0x00ffffff; if (rgb != 0) b |= (1 << row); } } out[col] = b; } } /** * Converts the given font to ledfont format written to a LEDFont. * * @param font the AWT font to convert * @param height the target height in pixels * @param offset an offset by which all glyphs should be raised or lowered * relative to the font baseline (this should usually be zero) * @return the converted LEDFont * @throws IOException if an error occurs */ public static LEDFont convert(Font font, int height, int offset) throws IOException { // prepare temp buffered image int imageWidth = height * 4; int imageHeight = height * 4; BufferedImage image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_ARGB); Graphics g = image.getGraphics(); g.setFont(font); FontMetrics metrics = g.getFontMetrics(); int baseline = metrics.getAscent(); if (baseline == 0) // a bug in the ttf file? baseline = height; // create font LEDFont ledfont = new LEDFont(height); // process all chars char[] chars = new char[2]; byte[] buf = new byte[256]; for (int c = 0; c <= Character.MAX_CODE_POINT; c++) { if (font.canDisplay(c)) { // draw glyph g.setColor(Color.BLACK); g.fillRect(0, 0, imageWidth, imageHeight); g.setColor(Color.WHITE); int charCount = Character.toChars(c, chars, 0); g.drawChars(chars, 0, charCount, 0, baseline); Rectangle2D bounds = metrics.getStringBounds(chars, 0, charCount, g); int width = (int)bounds.getWidth(); // write entry if (width > 0) { convertPixels(buf, image, baseline - offset, width, height); ledfont.addChar(c, buf, 0, width); } } } g.dispose(); return ledfont; } /** * Converts the given font to ledfont format written to a file. * * @param font the AWT font to convert * @param height the target height in pixels * @param offset an offset by which all glyphs should be raised or lowered * relative to the font baseline (this can usually be left at zero) * @param out the file to which the ledfont data is written * @throws IOException if an error occurs */ public static void convert(Font font, int height, int offset, File out) throws IOException { convert(font, height, offset).save(out); } /** * Parses a python script defining a font data structure. * * @param in the input stream containing the python script * @param height the font height * @return the parsed LEDFont * @throws IOException if an error occurs */ public static LEDFont parsePython(InputStream in, int height) throws IOException { String script = new String(Utils.readBytes(in), "UTF-8"); Matcher matcher = Pattern.compile("\\{(\\s*(\\d+)\\s*:\\s*\\[([\\s\\d,]*)\\][\\s,]*)*\\}").matcher(script); if (!matcher.find()) throw new IOException("invalid python font definition"); script = matcher.group().replaceAll("\\s", ""); LEDFont ledfont = new LEDFont(height); byte[] buf = new byte[256]; ledfont.addChar(' ', buf, 0, 3); // space char is hard-coded matcher = Pattern.compile("\\s*(\\d+)\\s*:\\s*\\[([\\s\\d,]*)\\][\\s,]*").matcher(script); while (matcher.find()) { int codePoint = Integer.parseInt(matcher.group(1)); String data = matcher.group(2).replace("\\s", ""); if (data.length() > 0) { String[] cols = data.split(","); int len = cols.length; for (int i = 0; i < len; i++) buf[i] = Byte.parseByte(cols[i]); buf[len++] = 0; // space between chars is hard-coded ledfont.addChar(codePoint, buf, 0, len); } } return ledfont; } /** * Converts a python script defining a font data structure to a LEDFont. * * @param in the input file containing the python script * @param height the font height * @param out the file to which the ledfont data is written * @throws IOException if an error occurs */ public static void convertFromPython(File in, int height, File out) throws IOException { parsePython(new FileInputStream(in), height).save(out); } /** * 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 { // parse args String in = null; File out = null; float points = 8; int height = 5; int offset = 0; boolean dump = false; String text = null; int i = 0; while (i < args.length) { String arg = args[i++]; if (i == args.length || arg.length() < 2 || arg.charAt(0) != '-') throw new IllegalArgumentException("invalid argument: " + arg); String val = args[i++]; switch (arg.charAt(1)) { case 'i': in = val; break; case 'o': out = new File(val); break; case 'p': points = Float.parseFloat(val); break; case 'h': height = Integer.parseInt(val); break; case 'f': offset = Integer.parseInt(val); break; case 't': text = val; break; case 'd': dump = val.equals("true"); break; default: throw new IllegalArgumentException("invalid argument: " + arg); } } if (in == null) { System.out.println("Usage: FontConverter -i input [-o output] [-p points] [-h height] [-f offset] [-t text] [-d true]"); System.exit(-1); } if (out == null) out = new File(in).getAbsoluteFile().getParentFile(); if (out.isDirectory()) out = new File(out, new File(in).getName().replaceAll("[.][^.]+$", "") + ".ledfont"); // process font conversion if (in.toLowerCase().endsWith(".py")) { convertFromPython(new File(in), height, out); } else { Font font = loadTTF(in); font = font.deriveFont(font.getStyle(), points); convert(font, height, offset, out); } if (text != null || dump) { LEDFont ledfont = new LEDFont(out.getAbsolutePath()); if (dump) text = ledfont.getSupportedCharsAsString(); System.out.println(ledfont.toAsciiArt(text, 80)); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy