2008-09-01 Colin D Bennett
Added BDF->PFF2 font conversion and BDF/PFF2 viewing tool.
* util/fonttool: New directory.
* util/fonttool/README.txt: New file.
* util/fonttool/build.sh: New file.
* util/fonttool/build.xml: New file.
* util/fonttool/fonttool.iml: New file.
* util/fonttool/fonttool.ipr: New file.
* util/fonttool/makepf2.sh: New file.
* util/fonttool/src: New directory.
* util/fonttool/src/org: New directory.
* util/fonttool/src/org/gnu: New directory.
* util/fonttool/src/org/gnu/grub: New directory.
* util/fonttool/src/org/gnu/grub/fonttool: New directory.
* util/fonttool/src/org/gnu/grub/fonttool/BDFFont.java: New file.
* util/fonttool/src/org/gnu/grub/fonttool/BDFLoader.java: New file.
* util/fonttool/src/org/gnu/grub/fonttool/CharDefs.java: New file.
* util/fonttool/src/org/gnu/grub/fonttool/CharMatrix.java: New file.
* util/fonttool/src/org/gnu/grub/fonttool/CharStorageInfo.java: New file.
* util/fonttool/src/org/gnu/grub/fonttool/CharView.java: New file.
* util/fonttool/src/org/gnu/grub/fonttool/Converter.java: New file.
* util/fonttool/src/org/gnu/grub/fonttool/FileFormatException.java: New file.
* util/fonttool/src/org/gnu/grub/fonttool/Font.java: New file.
* util/fonttool/src/org/gnu/grub/fonttool/Glyph.java: New file.
* util/fonttool/src/org/gnu/grub/fonttool/PFF2Loader.java: New file.
* util/fonttool/src/org/gnu/grub/fonttool/PFF2Sections.java: New file.
* util/fonttool/src/org/gnu/grub/fonttool/PFF2Writer.java: New file.
* util/fonttool/src/org/gnu/grub/fonttool/Viewer.java: New file.
* util/fonttool/src/org/gnu/grub/fonttool/ViewerFrame.java: New file.
* util/fonttool/test: New directory.
* util/fonttool/test/org: New directory.
* util/fonttool/test/org/gnu: New directory.
* util/fonttool/test/org/gnu/grub: New directory.
* util/fonttool/test/org/gnu/grub/fonttool: New directory.
* util/fonttool/test/org/gnu/grub/fonttool/TestGlyph.java: New file.
=== added directory 'util/fonttool'
=== added file 'util/fonttool/README.txt'
--- util/fonttool/README.txt 1970-01-01 00:00:00 +0000
+++ util/fonttool/README.txt 2008-09-01 17:13:43 +0000
@@ -0,0 +1,48 @@
+==============
+GRUB Font Tool
+==============
+
+Building
+========
+
+You can build the sources using the ``build.sh`` shell script, with::
+
+ sh build.sh
+
+or, you can use the Apache Ant tool to build the sources. The main benefit
+using Ant is its incremental compilation when files are modified. Simply run::
+
+ ant
+
+Using either method, the file 'build/fonttool.jar' is generated, and the Java
+'.class' files are stored in 'build/src/'.
+
+
+Running
+=======
+
+The font tool currently contains two programs: a viewer and a converter.
+
+
+Running the viewer
+------------------
+
+Run:
+
+ java -cp build/fonttool.jar org.gnu.grub.fonttool.Viewer myfont.pf2
+
+The font viewer can view either PFF2 or BDF format font files.
+
+
+Running the converter
+---------------------
+
+You can either run the converter directly, as ::
+
+ java -cp build/fonttool.jar org.gnu.grub.fonttool.Converter \
+ --in=a.bdf --out=b.pf2
+
+or you can use the 'makepf2.sh' script to do a batch conversion of multiple
+font files. This script converts all the BDF fonts specified on the command
+line into PFF2 (.pf2) fonts, storing them in the current directory.
+
=== added file 'util/fonttool/build.sh'
--- util/fonttool/build.sh 1970-01-01 00:00:00 +0000
+++ util/fonttool/build.sh 2008-09-01 17:11:00 +0000
@@ -0,0 +1,27 @@
+#!/bin/sh
+# Build script for font tool.
+
+die() {
+ echo "Error: $1"
+ exit 1
+}
+
+O=build
+SRC=src
+
+if [ -e "${O}" ]; then
+ echo "Cleaning output directory ${O}..."
+ rm -rf "${O}" || die "Can't delete output directory ${O}"
+fi
+
+mkdir -p "${O}" || die "Can't create output directory ${O}"
+
+echo "Compiling Java sources..."
+mkdir -p "${O}/src" || die "Can't create output src directory ${O}/src"
+javac -source 1.5 -target 1.5 -g -deprecation -encoding UTF-8 \
+ -d "${O}/src" \
+ $(find "${SRC}" -name '*.java') || die "Java compiler failed"
+
+echo "Building JAR file..."
+jar cf "${O}/fonttool.jar" -C "${O}/src" . || die "Could not create JAR file"
+
=== added file 'util/fonttool/build.xml'
--- util/fonttool/build.xml 1970-01-01 00:00:00 +0000
+++ util/fonttool/build.xml 2008-09-01 16:28:19 +0000
@@ -0,0 +1,23 @@
+
+ GRUB 2 font tool.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
=== added file 'util/fonttool/fonttool.iml'
--- util/fonttool/fonttool.iml 1970-01-01 00:00:00 +0000
+++ util/fonttool/fonttool.iml 2008-09-01 16:28:19 +0000
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
=== added file 'util/fonttool/fonttool.ipr'
--- util/fonttool/fonttool.ipr 1970-01-01 00:00:00 +0000
+++ util/fonttool/fonttool.ipr 2008-09-01 16:28:19 +0000
@@ -0,0 +1,306 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
=== added file 'util/fonttool/makepf2.sh'
--- util/fonttool/makepf2.sh 1970-01-01 00:00:00 +0000
+++ util/fonttool/makepf2.sh 2008-09-01 16:28:19 +0000
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+# This script will generate GRUB 2 PFF2 font files out of BDF.
+# You need the GRUB font utilities "Converter" program for this.
+
+DIR=$( dirname $0 )
+
+for font in "$@"
+do
+ out=`basename "${font}" | cut -d'.' -f1`
+ out="${out}.pf2"
+ echo "Converting ${font} -> ${out} ..."
+ java -cp "${DIR}/build/fonttool.jar" \
+ org.gnu.grub.fonttool.Converter \
+ --in="${font}" \
+ --out="${out}"
+done
=== added directory 'util/fonttool/src'
=== added directory 'util/fonttool/src/org'
=== added directory 'util/fonttool/src/org/gnu'
=== added directory 'util/fonttool/src/org/gnu/grub'
=== added directory 'util/fonttool/src/org/gnu/grub/fonttool'
=== added file 'util/fonttool/src/org/gnu/grub/fonttool/BDFFont.java'
--- util/fonttool/src/org/gnu/grub/fonttool/BDFFont.java 1970-01-01 00:00:00 +0000
+++ util/fonttool/src/org/gnu/grub/fonttool/BDFFont.java 2008-09-01 16:28:19 +0000
@@ -0,0 +1,24 @@
+package org.gnu.grub.fonttool;
+
+import java.awt.image.BufferedImage;
+import java.util.HashMap;
+import java.util.Map;
+
+public class BDFFont {
+ private Map glyphs = new HashMap();
+ private int height = 0;
+
+ void putGlyph(char ch, BufferedImage glyph) {
+ glyphs.put(ch, glyph);
+ if (glyph.getHeight() > height)
+ height = glyph.getHeight();
+ }
+
+ public BufferedImage getGlyph(char c) {
+ return glyphs.get(c);
+ }
+
+ public int getHeight() {
+ return height;
+ }
+}
=== added file 'util/fonttool/src/org/gnu/grub/fonttool/BDFLoader.java'
--- util/fonttool/src/org/gnu/grub/fonttool/BDFLoader.java 1970-01-01 00:00:00 +0000
+++ util/fonttool/src/org/gnu/grub/fonttool/BDFLoader.java 2008-09-01 16:28:19 +0000
@@ -0,0 +1,254 @@
+package org.gnu.grub.fonttool;
+
+import java.io.*;
+import java.util.StringTokenizer;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class BDFLoader {
+ private final BufferedReader in;
+ private final Font font;
+ private int maxCharWidth;
+ private int maxCharHeight;
+
+ BDFLoader(BufferedReader in) {
+ this.in = in;
+ this.font = new Font();
+ this.maxCharWidth = 0;
+ this.maxCharHeight = 0;
+ }
+
+ public static boolean isBDFFile(String filename) {
+ DataInputStream in = null;
+ try {
+ in = new DataInputStream(new FileInputStream(filename));
+ final String signature = "STARTFONT ";
+ byte[] b = new byte[signature.length()];
+ in.readFully(b);
+ in.close();
+
+ String s = new String(b, "US-ASCII");
+ return signature.equals(s);
+ } catch (IOException e) {
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException e1) {
+ // Ignore.
+ }
+ }
+ return false;
+ }
+ }
+
+ public static Font loadFontResource(String resourceName) throws IOException {
+ InputStream in = BDFLoader.class.getClassLoader().getResourceAsStream(resourceName);
+ if (in == null)
+ throw new FileNotFoundException("Font resource " + resourceName + " not found");
+ return loadFontFromStream(in);
+ }
+
+ public static Font loadFontFile(String filename) throws IOException {
+ InputStream in = new FileInputStream(filename);
+ return loadFontFromStream(in);
+ }
+
+ private static Font loadFontFromStream(InputStream inStream) throws IOException {
+ BufferedReader in;
+ try {
+ in = new BufferedReader(new InputStreamReader(inStream, "ISO-8859-1"));
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("Encoding not supported: " + e.getMessage(), e);
+ }
+ return new BDFLoader(in).loadFont();
+ }
+
+ private Font loadFont() throws IOException {
+ loadFontInfo();
+ while (loadChar()) {
+ /* Loop. */
+ }
+
+ font.setMaxCharWidth(maxCharWidth);
+ font.setMaxCharHeight(maxCharHeight);
+ return font;
+ }
+
+ private void loadFontInfo() throws IOException {
+ final Pattern stringSettingPattern = Pattern.compile("^(\\w+)\\s+\"([^\"]+)\"$");
+ String line;
+ // Load the global font information that appears before CHARS.
+ final int UNSET = Integer.MIN_VALUE;
+ font.setAscent(UNSET);
+ font.setDescent(UNSET);
+ font.setFamily("Unknown");
+ font.setBold(false);
+ font.setItalic(false);
+ font.setPointSize(Font.UNKNOWN_POINT_SIZE);
+ do {
+ line = in.readLine();
+ if (line == null)
+ throw new IOException("BDF format error: end of file while " +
+ "reading global font information");
+
+ StringTokenizer st = new StringTokenizer(line);
+ if (st.hasMoreTokens()) {
+ String name = st.nextToken();
+ if (name.equals("FONT_ASCENT")) {
+ if (!st.hasMoreTokens())
+ throw new IOException("BDF format error: " +
+ "no tokens after " + name);
+ font.setAscent(Integer.parseInt(st.nextToken()));
+ } else if (name.equals("FONT_DESCENT")) {
+ if (!st.hasMoreTokens())
+ throw new IOException("BDF format error: " +
+ "no tokens after " + name);
+ font.setDescent(Integer.parseInt(st.nextToken()));
+ } else if (name.equals("POINT_SIZE")) {
+ if (!st.hasMoreTokens())
+ throw new IOException("BDF format error: " +
+ "no tokens after " + name);
+ // Divide by 10, since it is stored X10.
+ font.setPointSize(Integer.parseInt(st.nextToken()) / 10);
+ } else if (name.equals("FAMILY_NAME")) {
+ Matcher matcher = stringSettingPattern.matcher(line);
+ if (!matcher.matches())
+ throw new IOException("BDF format error: " +
+ "line doesn't match string " +
+ "setting pattern: " + line);
+ font.setFamily(matcher.group(2));
+ } else if (name.equals("WEIGHT_NAME")) {
+ Matcher matcher = stringSettingPattern.matcher(line);
+ if (!matcher.matches())
+ throw new IOException("BDF format error: " +
+ "line doesn't match string " +
+ "setting pattern: " + line);
+ String weightName = matcher.group(2);
+ font.setBold("bold".equalsIgnoreCase(weightName));
+ } else if (name.equals("SLANT")) {
+ Matcher matcher = stringSettingPattern.matcher(line);
+ if (!matcher.matches())
+ throw new IOException("BDF format error: " +
+ "line doesn't match string " +
+ "setting pattern: " + line);
+ String slantType = matcher.group(2);
+ font.setItalic(!"R".equalsIgnoreCase(slantType));
+ } else if (name.equals("CHARS")) {
+ // This is the end of the global font information and
+ // the beginning of the character definitions.
+ break;
+ } else {
+ // Skip other fields.
+ }
+ }
+ } while (true);
+
+ if (font.getAscent() == UNSET)
+ throw new IOException("BDF format error: no FONT_ASCENT property");
+ if (font.getDescent() == UNSET)
+ throw new IOException("BDF format error: no FONT_DESCENT property");
+ }
+
+ private boolean loadChar() throws IOException {
+ String line;
+ // Find start of character
+ do {
+ line = in.readLine();
+ if (line == null)
+ return false;
+ StringTokenizer st = new StringTokenizer(line);
+ if (st.hasMoreTokens() && st.nextToken().equals("STARTCHAR")) {
+ if (!st.hasMoreTokens())
+ throw new IOException("BDF format error: no character name after STARTCHAR");
+ break;
+ }
+ } while (true);
+
+ // Find properties
+ final int UNSET = Integer.MIN_VALUE;
+ int codePoint = UNSET;
+ int bbx = UNSET;
+ int bby = UNSET;
+ int bbox = UNSET;
+ int bboy = UNSET;
+ int dwidth = UNSET;
+ do {
+ line = in.readLine();
+ if (line == null)
+ return false;
+ StringTokenizer st = new StringTokenizer(line);
+ if (st.hasMoreTokens()) {
+ String field = st.nextToken();
+ if (field.equals("ENCODING")) {
+ if (!st.hasMoreTokens())
+ throw new IOException("BDF format error: no encoding # after ENCODING");
+ String codePointStr = st.nextToken();
+ codePoint = Integer.parseInt(codePointStr);
+ } else if (field.equals("BBX")) {
+ if (!st.hasMoreTokens())
+ throw new IOException("BDF format error: no tokens after BBX");
+ bbx = Integer.parseInt(st.nextToken());
+ bby = Integer.parseInt(st.nextToken());
+ bbox = Integer.parseInt(st.nextToken());
+ bboy = Integer.parseInt(st.nextToken());
+ } else if (field.equals("DWIDTH")) {
+ if (!st.hasMoreTokens())
+ throw new IOException("BDF format error: no tokens after DWIDTH");
+ dwidth = Integer.parseInt(st.nextToken());
+ int dwidthY = Integer.parseInt(st.nextToken());
+ // The DWIDTH Y value should be zero for any normal font.
+ if (dwidthY != 0) {
+ throw new IOException("BDF format error: dwidth Y value" +
+ "is nonzero (" + dwidthY + ") " +
+ "for char " + codePoint + ".");
+ }
+ } else if (field.equals("BITMAP")) {
+ break; // now read the bitmap
+ }
+ }
+ } while (true);
+
+ if (codePoint == UNSET)
+ throw new IOException("BDF format error: " +
+ "no code point set");
+ if (bbx == UNSET || bby == UNSET)
+ throw new IOException("BDF format error: " +
+ "bbx/bby missing: " + bbx + ", " + bby +
+ " for char " + codePoint);
+
+ if (bbox == UNSET || bboy == UNSET)
+ throw new IOException("BDF format error: " +
+ "bbox/bboy missing: " + bbox + ", " + bboy +
+ " for char " + codePoint);
+
+ if (dwidth == UNSET)
+ throw new IOException("BDF format error: " +
+ "dwidth missing for char " + codePoint);
+
+ final int glyphWidth = bbx;
+ final int glyphHeight = bby;
+ if (glyphWidth > maxCharWidth)
+ maxCharWidth = glyphWidth;
+ if (glyphHeight > maxCharHeight)
+ maxCharHeight = glyphHeight;
+
+ // Read the bitmap
+ Glyph glyph = new Glyph(codePoint, glyphWidth, glyphHeight, bbox, bboy, dwidth);
+ for (int y = 0; y < glyphHeight; y++) {
+ line = in.readLine();
+ if (line == null)
+ return false;
+ for (int b = 0; b < line.length(); b++) {
+ int v = Integer.parseInt(Character.toString(line.charAt(b)), 16);
+ for (int x = b * 4, i = 0; i < 4 && x < glyphWidth; x++, i++) {
+ boolean set = (v & 0x8) != 0;
+ v <<= 1;
+ glyph.setPixel(x, y, set);
+ }
+ }
+ }
+
+ font.putGlyph(codePoint, glyph);
+ return true;
+ }
+}
=== added file 'util/fonttool/src/org/gnu/grub/fonttool/CharDefs.java'
--- util/fonttool/src/org/gnu/grub/fonttool/CharDefs.java 1970-01-01 00:00:00 +0000
+++ util/fonttool/src/org/gnu/grub/fonttool/CharDefs.java 2008-09-01 16:28:19 +0000
@@ -0,0 +1,135 @@
+package org.gnu.grub.fonttool;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.TreeMap;
+
+class CharDefs {
+ private boolean debug = "1".equals(System.getProperty("fonttool.debug"));
+ private TreeMap glyphs;
+ private ByteArrayOutputStream charDefsData;
+ private int maxCharWidth;
+ private int maxCharHeight;
+ private HashMap charIndex;
+
+ public CharDefs(Font font) {
+ this.glyphs = font.getGlyphs();
+ this.charIndex = null;
+ this.charDefsData = null;
+
+ calculateMaxSizes();
+ }
+
+ private void calculateMaxSizes() {
+ maxCharWidth = 0;
+ maxCharHeight = 0;
+ for (Glyph glyph : glyphs.values()) {
+ final int w = glyph.getWidth();
+ final int h = glyph.getHeight();
+ if (w > maxCharWidth)
+ maxCharWidth = w;
+ if (h > maxCharHeight)
+ maxCharHeight = h;
+ }
+ }
+
+ void buildDefinitions() {
+ charIndex = new HashMap();
+ HashMap charDefIndex = new HashMap();
+ charDefsData = new ByteArrayOutputStream();
+ DataOutputStream charDefs = new DataOutputStream(charDefsData);
+ try {
+ // Loop through all the glyphs, writing the glyph data to the
+ // in-memory byte stream, collapsing duplicate glyphs, and
+ // constructing index information.
+ for (Glyph glyph : glyphs.values()) {
+ CharDef charDef = new CharDef(glyph.getWidth(),
+ glyph.getHeight(),
+ glyph.getBitmap());
+
+ if (charDefIndex.containsKey(charDef)) {
+ // Use already-written glyph.
+ if (debug)
+ System.out.printf("Duplicate glyph for character U+%04X%n",
+ glyph.getCodePoint());
+ final int charOffset = charDefIndex.get(charDef).intValue();
+ final CharStorageInfo info =
+ new CharStorageInfo(glyph.getCodePoint(), charOffset);
+ charIndex.put(glyph.getCodePoint(), info);
+ } else {
+ // Write glyph data.
+ final int charOffset = charDefs.size();
+ final CharStorageInfo info =
+ new CharStorageInfo(glyph.getCodePoint(), charOffset);
+ charIndex.put(glyph.getCodePoint(), info);
+
+ charDefIndex.put(charDef, (long) charOffset);
+
+ charDefs.writeShort(glyph.getWidth());
+ charDefs.writeShort(glyph.getHeight());
+ charDefs.writeShort(glyph.getBbox());
+ charDefs.writeShort(glyph.getBboy());
+ charDefs.writeShort(glyph.getDeviceWidth());
+ charDefs.write(glyph.getBitmap());
+ }
+ }
+ } catch (IOException e) {
+ throw new RuntimeException("Error writing to in-memory byte stream", e);
+ }
+ }
+
+ public int getMaxCharWidth() {
+ return maxCharWidth;
+ }
+
+ public int getMaxCharHeight() {
+ return maxCharHeight;
+ }
+
+ public HashMap getCharIndex() {
+ if (charIndex == null) throw new IllegalStateException();
+ return charIndex;
+ }
+
+ public byte[] getDefinitionData() {
+ if (charDefsData == null) throw new IllegalStateException();
+ return charDefsData.toByteArray();
+ }
+
+
+ private static class CharDef {
+ private final int width;
+ private final int height;
+ private final byte[] data;
+
+ public CharDef(int width, int height, byte[] data) {
+ this.width = width;
+ this.height = height;
+ this.data = data;
+ }
+
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ CharDef charDef = (CharDef) o;
+
+ if (height != charDef.height) return false;
+ if (width != charDef.width) return false;
+ if (!Arrays.equals(data, charDef.data)) return false;
+
+ return true;
+ }
+
+ public int hashCode() {
+ int result;
+ result = width;
+ result = 31 * result + height;
+ result = 31 * result + Arrays.hashCode(data);
+ return result;
+ }
+ }
+}
=== added file 'util/fonttool/src/org/gnu/grub/fonttool/CharMatrix.java'
--- util/fonttool/src/org/gnu/grub/fonttool/CharMatrix.java 1970-01-01 00:00:00 +0000
+++ util/fonttool/src/org/gnu/grub/fonttool/CharMatrix.java 2008-09-01 16:28:19 +0000
@@ -0,0 +1,139 @@
+package org.gnu.grub.fonttool;
+
+import java.awt.*;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionAdapter;
+import java.util.HashMap;
+import java.util.Map;
+import javax.swing.*;
+
+public class CharMatrix extends JComponent {
+ private Font charFont;
+ private int zoom = 1;
+ private int startingChar;
+ private Map glyphLocations = new HashMap();
+ private GlyphSelectionListener glyphSelectionListener;
+ private Point mouseLocation;
+
+ public interface GlyphSelectionListener {
+ void glyphSelected(int codePoint);
+ }
+
+ public CharMatrix() {
+ addMouseListener(new MouseAdapter() {
+ public void mousePressed(MouseEvent e) {
+ int codePoint = getCodePointForGlyphAt(e.getPoint());
+ if (codePoint != -1) {
+ if (glyphSelectionListener != null)
+ glyphSelectionListener.glyphSelected(codePoint);
+ }
+ }
+
+ public void mouseExited(MouseEvent e) {
+ mouseLocation = null;
+ repaint();
+ }
+ });
+
+ addMouseMotionListener(new MouseMotionAdapter() {
+ public void mouseMoved(MouseEvent e) {
+ mouseLocation = e.getPoint();
+ repaint();
+ }
+ });
+ }
+
+ public void setGlyphSelectionListener(GlyphSelectionListener listener) {
+ this.glyphSelectionListener = listener;
+ }
+
+ public Font getCharFont() {
+ return charFont;
+ }
+
+ public void setCharFont(Font font) {
+ this.charFont = font;
+ repaint();
+ }
+
+ public int getZoom() {
+ return zoom;
+ }
+
+ public void setZoom(int zoom) {
+ this.zoom = zoom;
+ repaint();
+ }
+
+ public int getStartingChar() {
+ return startingChar;
+ }
+
+ public void setStartingChar(int startingChar) {
+ this.startingChar = startingChar;
+ repaint();
+ }
+
+ public int getCodePointForGlyphAt(Point p) {
+ for (Rectangle rectangle : glyphLocations.keySet()) {
+ if (rectangle.contains(p))
+ return glyphLocations.get(rectangle);
+ }
+ return -1;
+ }
+
+ protected void paintComponent(Graphics g) {
+ glyphLocations.clear();
+
+ if (charFont == null)
+ return;
+
+ int w = getWidth();
+ int h = getHeight();
+ int maxCharWidth = charFont.getMaxCharWidth();
+ int maxCharHeight = charFont.getMaxCharHeight();
+ int space = 2;
+ int charsPerRow = w / (maxCharWidth + space);
+ int rows = h / (maxCharHeight + space);
+
+ Rectangle highlightBounds = null;
+ int codePoint = startingChar;
+ int y = 3 + charFont.getAscent(); // Point y to the baseline.
+ for (int row = 0; row < rows; row++) {
+ int x = 3;
+ for (int col = 0; col < charsPerRow; col++) {
+ Glyph glyph = charFont.getGlyph(codePoint);
+
+ if (glyph != null) {
+ Rectangle charBounds =
+ CharView.drawGlyph(g, charFont, glyph, x, y, zoom, getForeground());
+ // Expand the bounding box for a bigger clickable area.
+ final int boundsPad = 1;
+ charBounds.x -= boundsPad;
+ charBounds.y -= boundsPad;
+ charBounds.width += 2 * boundsPad;
+ charBounds.height += 2 * boundsPad;
+ glyphLocations.put(charBounds, codePoint);
+ if (mouseLocation != null && charBounds.contains(mouseLocation)) {
+ highlightBounds = charBounds;
+ }
+ }
+ x += maxCharWidth + space;
+ codePoint++;
+ }
+
+ y += maxCharHeight + space;
+ }
+
+ if (highlightBounds != null) {
+ g.setColor(Color.blue);
+ Graphics2D g2 = (Graphics2D) g;
+ g2.setStroke(new BasicStroke(3f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 2f));
+ g2.draw(new Rectangle(highlightBounds.x - 2, highlightBounds.y - 2,
+ highlightBounds.width + 3, highlightBounds.height + 3));
+ g2.setStroke(new BasicStroke(1f));
+ }
+
+ }
+}
=== added file 'util/fonttool/src/org/gnu/grub/fonttool/CharStorageInfo.java'
--- util/fonttool/src/org/gnu/grub/fonttool/CharStorageInfo.java 1970-01-01 00:00:00 +0000
+++ util/fonttool/src/org/gnu/grub/fonttool/CharStorageInfo.java 2008-09-01 16:28:19 +0000
@@ -0,0 +1,19 @@
+package org.gnu.grub.fonttool;
+
+class CharStorageInfo {
+ private final int codePoint;
+ private final int fileOffset;
+
+ public CharStorageInfo(int codePoint, int fileOffset) {
+ this.codePoint = codePoint;
+ this.fileOffset = fileOffset;
+ }
+
+ public int getCodePoint() {
+ return codePoint;
+ }
+
+ public int getFileOffset() {
+ return fileOffset;
+ }
+}
=== added file 'util/fonttool/src/org/gnu/grub/fonttool/CharView.java'
--- util/fonttool/src/org/gnu/grub/fonttool/CharView.java 1970-01-01 00:00:00 +0000
+++ util/fonttool/src/org/gnu/grub/fonttool/CharView.java 2008-09-01 16:28:19 +0000
@@ -0,0 +1,101 @@
+package org.gnu.grub.fonttool;
+
+import java.awt.*;
+import java.util.Formatter;
+import javax.swing.*;
+
+public class CharView extends JComponent {
+ private Font charFont = null;
+ private Glyph glyph = null;
+ private int zoom = 1;
+
+ public Font getCharFont() {
+ return charFont;
+ }
+
+ public void setCharFont(Font charFont) {
+ this.charFont = charFont;
+ }
+
+ public Glyph getGlyph() {
+ return glyph;
+ }
+
+ public void setGlyph(Glyph glyph) {
+ this.glyph = glyph;
+ repaint();
+ }
+
+ public void setZoom(int zoom) {
+ this.zoom = zoom;
+ }
+
+ public Dimension getPreferredSize() {
+ if (glyph == null)
+ return new Dimension(1, 1);
+ return new Dimension(glyph.getWidth() * zoom, glyph.getHeight() * zoom);
+ }
+
+ protected void paintComponent(Graphics g) {
+ if (glyph == null) {
+ // Draw a gray 'X' indicating no character selected.
+ g.setColor(Color.gray);
+ g.drawLine(0, 0, getWidth() - 1, getHeight() - 1);
+ g.drawLine(getWidth(), 0, 0, getHeight() - 1);
+ return;
+ }
+
+ final int x0 = (getWidth() - glyph.getWidth() * zoom) / 2;
+ final int y0 = getHeight() / 2;
+ drawGlyph(g, charFont, glyph, x0, y0, zoom, getForeground());
+
+ g.setFont(new java.awt.Font("SansSerif", java.awt.Font.PLAIN, 12));
+ FontMetrics fm = g.getFontMetrics();
+ Formatter f = new Formatter();
+ f.format("U+%04X", glyph.getCodePoint());
+ String str = f.toString();
+ g.drawString(str,
+ (getWidth() - fm.stringWidth(str)) / 2,
+ getHeight() - fm.getHeight());
+ }
+
+ /**
+ * Draw the specified glyph with its baseline at y0 and the horizontal
+ * start at x0.
+ */
+ static Rectangle drawGlyph(Graphics g, Font font, Glyph glyph, int startX, int baselineY, int zoom, Color fg) {
+ // Adjust (x0, y0) so that it puts the bitmap at the right location.
+ final int offsetX = zoom * glyph.getBbox();
+ final int offsetY = zoom * glyph.getBboy();
+ final int bitmapLeft = startX + offsetX;
+ final int bitmapBottom = baselineY - offsetY;
+ final int bitmapTop = bitmapBottom - zoom * glyph.getHeight();
+
+ // Draw a gray box around the glyph bounds.
+ g.setColor(Color.lightGray);
+ g.drawRect(bitmapLeft - 1, bitmapTop - 1,
+ glyph.getWidth() * zoom + 1, zoom * glyph.getHeight() + 1);
+
+ // Draw the baseline
+ g.setColor(new Color(110, 110, 255));
+ g.drawLine(startX, baselineY, startX + zoom * glyph.getDeviceWidth(), baselineY);
+
+ // Draw the glyph.
+ g.setColor(fg);
+ byte[] bitmap = glyph.getBitmap();
+ int bitIndex = 0;
+ for (int y = 0; y < glyph.getHeight(); y++) {
+ for (int x = 0; x < glyph.getWidth(); x++, bitIndex++) {
+ int byteIndex = bitIndex / 8;
+ int bitShift = bitIndex % 8;
+ byte b = bitmap[byteIndex];
+ boolean bitValue = (b & (0x80 >>> bitShift)) != 0;
+ if (bitValue) {
+ g.fillRect(bitmapLeft + x * zoom, bitmapTop + y * zoom, zoom, zoom);
+ }
+ }
+ }
+
+ return new Rectangle(bitmapLeft, bitmapTop, zoom * glyph.getWidth(), zoom * glyph.getHeight());
+ }
+}
=== added file 'util/fonttool/src/org/gnu/grub/fonttool/Converter.java'
--- util/fonttool/src/org/gnu/grub/fonttool/Converter.java 1970-01-01 00:00:00 +0000
+++ util/fonttool/src/org/gnu/grub/fonttool/Converter.java 2008-09-01 16:28:19 +0000
@@ -0,0 +1,78 @@
+package org.gnu.grub.fonttool;
+
+import java.io.IOException;
+
+/**
+ * Program to convert BDF fonts into PFF2 fonts for use with GRUB.
+ */
+public class Converter {
+ public static void main(String[] args) {
+ if (args.length < 1) {
+ printUsageAndExit();
+ }
+
+ String in = null;
+ String out = null;
+
+ try {
+ for (String arg : args) {
+ if (arg.startsWith("--")) {
+ String option;
+ String value;
+ int equalsPos = arg.indexOf('=');
+ if (equalsPos < 0) {
+ option = arg.substring(2);
+ value = null;
+ } else {
+ option = arg.substring(2, equalsPos);
+ value = arg.substring(equalsPos + 1);
+ }
+
+ if ("in".equals(option)) {
+ if (value == null)
+ throw new CommandLineException(option + " option requires a value.");
+ in = value;
+ } else if ("out".equals(option)) {
+ if (value == null)
+ throw new CommandLineException(option + " option requires a value.");
+ out = value;
+ }
+ } else {
+ throw new CommandLineException("Non-option argument `" + arg + "'.");
+ }
+ }
+ if (in == null || out == null) {
+ throw new CommandLineException("Both --in=X and --out=Y must be specified.");
+ }
+ } catch (CommandLineException e) {
+ System.err.println("Error: " + e.getMessage());
+ System.exit(1);
+ }
+
+ try {
+ // Read BDF.
+ Font font = BDFLoader.loadFontFile(in);
+
+ // Write PFF2.
+ new PFF2Writer(out).writeFont(font);
+ } catch (IOException e) {
+ System.err.println("I/O error converting font: " + e);
+ e.printStackTrace();
+ System.exit(1);
+ }
+ }
+
+ private static class CommandLineException extends Exception {
+ public CommandLineException(String message) {
+ super(message);
+ }
+ }
+
+ private static void printUsageAndExit() {
+ System.err.println("GNU GRUB Font Conversion Tool");
+ System.err.println();
+ System.err.println("Usage: Converter --in=IN.bdf --out=OUT.pf2");
+ System.err.println();
+ System.exit(1);
+ }
+}
=== added file 'util/fonttool/src/org/gnu/grub/fonttool/FileFormatException.java'
--- util/fonttool/src/org/gnu/grub/fonttool/FileFormatException.java 1970-01-01 00:00:00 +0000
+++ util/fonttool/src/org/gnu/grub/fonttool/FileFormatException.java 2008-09-01 16:28:19 +0000
@@ -0,0 +1,9 @@
+package org.gnu.grub.fonttool;
+
+import java.io.IOException;
+
+public class FileFormatException extends IOException {
+ public FileFormatException(String msg) {
+ super(msg);
+ }
+}
=== added file 'util/fonttool/src/org/gnu/grub/fonttool/Font.java'
--- util/fonttool/src/org/gnu/grub/fonttool/Font.java 1970-01-01 00:00:00 +0000
+++ util/fonttool/src/org/gnu/grub/fonttool/Font.java 2008-09-01 16:28:19 +0000
@@ -0,0 +1,106 @@
+package org.gnu.grub.fonttool;
+
+import java.util.TreeMap;
+
+public class Font {
+ public static final int UNKNOWN_POINT_SIZE = -1;
+
+ private TreeMap glyphs;
+ private String family;
+ private boolean bold;
+ private boolean italic;
+ private int pointSize;
+ private int maxCharWidth;
+ private int maxCharHeight;
+ private int ascent;
+ private int descent;
+
+ public Font() {
+ glyphs = new TreeMap();
+ }
+
+ public String getFamily() {
+ return family;
+ }
+
+ public void setFamily(String family) {
+ this.family = family;
+ }
+
+ public boolean isBold() {
+ return bold;
+ }
+
+ public void setBold(boolean bold) {
+ this.bold = bold;
+ }
+
+ public boolean isItalic() {
+ return italic;
+ }
+
+ public void setItalic(boolean italic) {
+ this.italic = italic;
+ }
+
+ public int getPointSize() {
+ return pointSize;
+ }
+
+ public void setPointSize(int pointSize) {
+ this.pointSize = pointSize;
+ }
+
+ public int getMaxCharWidth() {
+ return maxCharWidth;
+ }
+
+ public void setMaxCharWidth(int maxCharWidth) {
+ this.maxCharWidth = maxCharWidth;
+ }
+
+ public int getMaxCharHeight() {
+ return maxCharHeight;
+ }
+
+ public void setMaxCharHeight(int maxCharHeight) {
+ this.maxCharHeight = maxCharHeight;
+ }
+
+ public int getAscent() {
+ return ascent;
+ }
+
+ public void setAscent(int ascent) {
+ this.ascent = ascent;
+ }
+
+ public int getDescent() {
+ return descent;
+ }
+
+ public void setDescent(int descent) {
+ this.descent = descent;
+ }
+
+ public void putGlyph(int codePoint, Glyph glyph) {
+ glyphs.put(codePoint, glyph);
+ }
+
+ public TreeMap getGlyphs() {
+ return glyphs;
+ }
+
+ public Glyph getGlyph(int codePoint) {
+ return glyphs.get(codePoint);
+ }
+
+ public String getStandardName() {
+ StringBuilder name = new StringBuilder(getFamily());
+ if (isBold()) name.append(" Bold");
+ if (isItalic()) name.append(" Italic");
+ name.append(' ');
+ name.append(getPointSize());
+ return name.toString();
+ }
+}
=== added file 'util/fonttool/src/org/gnu/grub/fonttool/Glyph.java'
--- util/fonttool/src/org/gnu/grub/fonttool/Glyph.java 1970-01-01 00:00:00 +0000
+++ util/fonttool/src/org/gnu/grub/fonttool/Glyph.java 2008-09-01 16:28:19 +0000
@@ -0,0 +1,83 @@
+package org.gnu.grub.fonttool;
+
+public class Glyph {
+ private final int codePoint;
+ private final int width;
+ private final int height;
+
+ // These define the amounts to shift the character bitmap by
+ // before drawing it. See
+ // http://www.adobe.com/devnet/font/pdfs/5005.BDF_Spec.pdf
+ // and
+ // http://www.linuxbabble.com/documentation/x/bdf/
+ // for explanatory figures.
+ private final int bbox;
+ private final int bboy;
+
+ // Number of pixels to advance horizontally from this character's origin
+ // to the origin of the next character.
+ private final int deviceWidth;
+
+ // Row-major order, no padding. Rows can break within a byte.
+ // MSb is first (leftmost/uppermost) pixel.
+ private final byte[] bitmap;
+
+ public Glyph(int codePoint, int width, int height, int bbox, int bboy, int deviceWidth) {
+ this(codePoint, width, height, bbox, bboy, deviceWidth,
+ new byte[(width * height + 7) / 8]);
+ }
+
+ public Glyph(int codePoint, int width, int height,
+ int bbox, int bboy, int deviceWidth,
+ byte[] bitmap) {
+ this.codePoint = codePoint;
+ this.width = width;
+ this.height = height;
+ this.bboy = bboy;
+ this.bbox = bbox;
+ this.deviceWidth = deviceWidth;
+ this.bitmap = bitmap;
+ }
+
+ public void setPixel(int x, int y, boolean value) {
+ if (x < 0 || y < 0 || x >= width || y >= height)
+ throw new IllegalArgumentException(
+ "Invalid pixel location (" + x + ", " + y + ") for "
+ + width + "x" + height + " glyph");
+
+ int bitIndex = y * width + x;
+ int byteIndex = bitIndex / 8;
+ int bitPos = bitIndex % 8;
+ int v = value ? 0x80 >>> bitPos : 0;
+ int mask = ~(0x80 >>> bitPos);
+ bitmap[byteIndex] = (byte) ((bitmap[byteIndex] & mask) | v);
+ }
+
+ public int getCodePoint() {
+ return codePoint;
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ public int getHeight() {
+ return height;
+ }
+
+ public int getBbox() {
+ return bbox;
+ }
+
+ public int getBboy() {
+ return bboy;
+ }
+
+ public int getDeviceWidth() {
+ return deviceWidth;
+ }
+
+ public byte[] getBitmap() {
+ return bitmap;
+ }
+}
=== added file 'util/fonttool/src/org/gnu/grub/fonttool/PFF2Loader.java'
--- util/fonttool/src/org/gnu/grub/fonttool/PFF2Loader.java 1970-01-01 00:00:00 +0000
+++ util/fonttool/src/org/gnu/grub/fonttool/PFF2Loader.java 2008-09-01 16:28:19 +0000
@@ -0,0 +1,203 @@
+package org.gnu.grub.fonttool;
+
+import java.io.EOFException;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.charset.Charset;
+
+/**
+ * Load a PFF2 font completely into memory as an instance of Font.
+ */
+public class PFF2Loader {
+ private static final int debug
+ = Integer.parseInt(System.getProperty("fonttool.debug", "0"));
+
+ private RandomAccessFile f;
+ private Font font;
+
+ private PFF2Loader(String filename) throws FileNotFoundException {
+ f = new RandomAccessFile(filename, "r");
+ font = new Font();
+ }
+
+ public static boolean isPFF2File(String filename) {
+ RandomAccessFile f = null;
+ boolean isFont = false;
+ try {
+ f = new RandomAccessFile(filename, "r");
+ byte[] sectionNameBytes = new byte[4];
+ f.readFully(sectionNameBytes);
+ String sectionName = new String(sectionNameBytes, "US-ASCII");
+ if (sectionName.equals(PFF2Sections.FILE)) {
+ final String expectedType = "PFF2";
+ int len = f.readInt();
+ if (len == expectedType.length()) {
+ byte[] typeBytes = new byte[len];
+ f.readFully(typeBytes);
+ String type = new String(typeBytes, "US-ASCII");
+ if (type.equals(expectedType)) {
+ isFont = true;
+ }
+ }
+ }
+ f.close();
+ } catch (IOException e) {
+ if (f != null) {
+ try {
+ f.close();
+ } catch (IOException e1) {
+ // Ignore.
+ }
+ }
+ }
+
+ return isFont;
+ }
+
+ public static Font loadFontFile(String filename) throws IOException {
+ PFF2Loader loader = new PFF2Loader(filename);
+ loader.loadFont();
+ return loader.getFont();
+ }
+
+ private void loadFont() throws IOException {
+ String s;
+ s = openSection();
+ if (!PFF2Sections.FILE.equals(s))
+ throw new FileFormatException(
+ "First PFF2 section must be FILE but is `" + s + "'.");
+
+ s = readCompleteSectionAsString();
+ if (!"PFF2".equals(s))
+ throw new FileFormatException("Unsupported file type `" + s + "'.");
+
+ font.setFamily("Unnamed");
+ font.setPointSize(Font.UNKNOWN_POINT_SIZE);
+ try {
+ if (debug >= 1) System.out.println("Loading font information...");
+ boolean doneLoadingInfo = false;
+ while (!doneLoadingInfo) {
+ s = openSection();
+ int sectionLength = f.readInt();
+ if (debug >= 2)
+ System.out.println("Section: " + s + " length: " + sectionLength);
+ if (s.equals(PFF2Sections.MAX_CHAR_WIDTH)) {
+ int maxCharWidth = readSectionContentsAsUShort(sectionLength);
+ font.setMaxCharWidth(maxCharWidth);
+ } else if (s.equals(PFF2Sections.MAX_CHAR_HEIGHT)) {
+ int maxCharHeight = readSectionContentsAsUShort(sectionLength);
+ font.setMaxCharHeight(maxCharHeight);
+ } else if (s.equals(PFF2Sections.FONT_POINT_SIZE)) {
+ int pointSize = readSectionContentsAsUShort(sectionLength);
+ font.setPointSize(pointSize);
+ } else if (s.equals(PFF2Sections.FONT_ASCENT)) {
+ int ascent = readSectionContentsAsUShort(sectionLength);
+ font.setAscent(ascent);
+ } else if (s.equals(PFF2Sections.FONT_DESCENT)) {
+ int descent = readSectionContentsAsUShort(sectionLength);
+ font.setDescent(descent);
+ } else if (s.equals(PFF2Sections.FONT_NAME)) {
+ String fontName = readSectionContentsAsString(sectionLength);
+ if (debug >= 1)
+ System.out.println("Loaded font: " + fontName);
+ } else if (s.equals(PFF2Sections.FONT_FAMILY)) {
+ font.setFamily(readSectionContentsAsString(sectionLength));
+ } else if (s.equals(PFF2Sections.FONT_WEIGHT)) {
+ String weightString = readSectionContentsAsString(sectionLength);
+ font.setBold(weightString.equalsIgnoreCase("bold"));
+ } else if (s.equals(PFF2Sections.FONT_SLANT)) {
+ String slantString = readSectionContentsAsString(sectionLength);
+ font.setItalic(slantString.equalsIgnoreCase("italic"));
+ } else if (s.equals(PFF2Sections.CHAR_INDEX)) {
+ loadCharsFromIndex(sectionLength);
+ } else if (s.equals(PFF2Sections.REMAINDER_IS_DATA)) {
+ doneLoadingInfo = true;
+ } else {
+ // If the section is not handled, then we just skip it.
+ if (f.skipBytes(sectionLength) != sectionLength) {
+ System.err.println("Warning: Could not skip "
+ + sectionLength
+ + " bytes for section `" + s + "'.");
+ }
+ }
+ }
+ } catch (EOFException eofException) {
+ // End of file; done reading font.
+ }
+ f.close();
+ }
+
+ private void loadCharsFromIndex(int sectionLength) throws IOException {
+ long indexSectionEnd = f.getFilePointer() + sectionLength;
+ try {
+ while (f.getFilePointer() < indexSectionEnd) {
+ int codePoint = f.readInt();
+ int storageFlags = f.readUnsignedByte();
+ int offset = f.readInt();
+
+ if ((storageFlags & 0x07) == 0) {
+ Glyph g = loadUncompressedGlyph(codePoint, offset);
+ font.putGlyph(codePoint, g);
+ }
+ }
+ } catch (EOFException eofException) {
+ // End of file; simple return.
+ }
+ }
+
+ private Glyph loadUncompressedGlyph(int codePoint, int fileOffset) throws IOException {
+ if (debug >= 3)
+ System.out.printf("Loading uncompressed glyph U+%04X at %d%n", codePoint, fileOffset);
+
+ long savedPos = f.getFilePointer();
+
+ f.seek(fileOffset);
+ int width = f.readUnsignedShort();
+ int height = f.readUnsignedShort();
+ int offsetX = f.readShort();
+ int offsetY = f.readShort();
+ int deviceWidth = f.readShort();
+ if (debug >= 3) System.out.println(" Size=" + width + "x" + height
+ + " offset=(" + offsetX + ", " + offsetY
+ + ") dWidth=" + deviceWidth);
+ byte[] bitmap = new byte[(width * height + 7) / 8];
+ f.readFully(bitmap);
+
+ f.seek(savedPos);
+ return new Glyph(codePoint, width, height,
+ offsetX, offsetY, deviceWidth, bitmap);
+ }
+
+ private int readSectionContentsAsUShort(int sectionLength) throws IOException {
+ if (sectionLength != 2)
+ throw new FileFormatException(
+ "For uint16 section, expected 2 bytes but length is "
+ + sectionLength + ".");
+ return f.readUnsignedShort();
+ }
+
+ private String openSection() throws IOException {
+ byte[] b = new byte[4];
+ f.readFully(b);
+ return new String(b, Charset.forName("US-ASCII"));
+ }
+
+ private String readCompleteSectionAsString() throws IOException {
+ int n = f.readInt();
+ return readSectionContentsAsString(n);
+ }
+
+ private String readSectionContentsAsString(int n) throws IOException {
+ StringBuilder buf = new StringBuilder();
+ while (n-- != 0) {
+ char c = (char) (f.readByte() & 0xFF);
+ buf.append(c);
+ }
+ return buf.toString();
+ }
+
+ public Font getFont() {
+ return font;
+ }
+}
=== added file 'util/fonttool/src/org/gnu/grub/fonttool/PFF2Sections.java'
--- util/fonttool/src/org/gnu/grub/fonttool/PFF2Sections.java 1970-01-01 00:00:00 +0000
+++ util/fonttool/src/org/gnu/grub/fonttool/PFF2Sections.java 2008-09-01 16:28:19 +0000
@@ -0,0 +1,19 @@
+package org.gnu.grub.fonttool;
+
+/**
+ * Section name constants for the PFF2 file format.
+ */
+public class PFF2Sections {
+ static final String FILE = "FILE";
+ static final String FONT_NAME = "NAME";
+ static final String FONT_FAMILY = "FAMI";
+ static final String FONT_WEIGHT = "WEIG";
+ static final String FONT_SLANT = "SLAN";
+ static final String FONT_POINT_SIZE = "PTSZ";
+ static final String MAX_CHAR_WIDTH = "MAXW";
+ static final String MAX_CHAR_HEIGHT = "MAXH";
+ static final String FONT_ASCENT = "ASCE";
+ static final String FONT_DESCENT = "DESC";
+ static final String CHAR_INDEX = "CHIX";
+ static final String REMAINDER_IS_DATA = "DATA";
+}
=== added file 'util/fonttool/src/org/gnu/grub/fonttool/PFF2Writer.java'
--- util/fonttool/src/org/gnu/grub/fonttool/PFF2Writer.java 1970-01-01 00:00:00 +0000
+++ util/fonttool/src/org/gnu/grub/fonttool/PFF2Writer.java 2008-09-01 16:28:19 +0000
@@ -0,0 +1,133 @@
+package org.gnu.grub.fonttool;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+// TODO Add DEFLATE compressed blocks of characters.
+public class PFF2Writer {
+ private RandomAccessFile f;
+ private String currentSection;
+ private long currentSectionStart;
+
+ public PFF2Writer(String filename) throws FileNotFoundException {
+ this.f = new RandomAccessFile(filename, "rw");
+ this.currentSection = null;
+ this.currentSectionStart = -1;
+ }
+
+ public void writeFont(Font font) throws IOException {
+ // Write file type ID header.
+ writeSection(PFF2Sections.FILE, "PFF2");
+
+ writeSection(PFF2Sections.FONT_NAME, font.getStandardName());
+ writeSection(PFF2Sections.FONT_FAMILY, font.getFamily());
+ writeSection(PFF2Sections.FONT_WEIGHT, font.isBold() ? "bold" : "normal");
+ writeSection(PFF2Sections.FONT_SLANT, font.isItalic() ? "italic" : "normal");
+ if (font.getPointSize() != Font.UNKNOWN_POINT_SIZE)
+ writeShortSection(PFF2Sections.FONT_POINT_SIZE, font.getPointSize());
+
+ // Construct character definitions.
+ CharDefs charDefs = new CharDefs(font);
+ charDefs.buildDefinitions();
+
+ // Write max character width and height metrics.
+ writeShortSection(PFF2Sections.MAX_CHAR_WIDTH, charDefs.getMaxCharWidth());
+ writeShortSection(PFF2Sections.MAX_CHAR_HEIGHT, charDefs.getMaxCharHeight());
+ writeShortSection(PFF2Sections.FONT_ASCENT, font.getAscent());
+ writeShortSection(PFF2Sections.FONT_DESCENT, font.getDescent());
+
+ // Write character index with pointers to the character definitions.
+ beginSection(PFF2Sections.CHAR_INDEX);
+
+ // Determine the size of the index, so we can properly refer to the
+ // character definition offset in the index. The actual number of
+ // bytes written is compared to the calculated value to ensure we
+ // are correct.
+ final int indexStart = (int) f.getFilePointer();
+ final int calculatedIndexLength =
+ font.getGlyphs().size() * (4 + 1 + 4);
+ final int charDefStart = indexStart + calculatedIndexLength + 8;
+
+ for (CharStorageInfo storageInfo : charDefs.getCharIndex().values()) {
+ f.writeInt(storageInfo.getCodePoint());
+ f.writeByte(0); // Storage flags: bits 1..0 = 00b : uncompressed.
+ f.writeInt(charDefStart + storageInfo.getFileOffset());
+ }
+
+ final int indexEnd = (int) f.getFilePointer();
+ if (indexEnd - indexStart != calculatedIndexLength) {
+ throw new RuntimeException("Incorrect index length calculated, calc="
+ + calculatedIndexLength
+ + " actual=" + (indexEnd - indexStart));
+ }
+ endSection(PFF2Sections.CHAR_INDEX);
+
+ f.writeBytes(PFF2Sections.REMAINDER_IS_DATA);
+ f.writeInt(-1); // Data takes up the rest of the file.
+ f.write(charDefs.getDefinitionData());
+
+ f.close();
+ }
+
+ private void beginSection(String sectionName) throws IOException {
+ verifyOkToBeginSection(sectionName);
+
+ f.writeBytes(sectionName);
+ f.writeInt(-1); // Placeholder for the section length.
+ currentSection = sectionName;
+ currentSectionStart = f.getFilePointer();
+ }
+
+ private void endSection(String sectionName) throws IOException {
+ verifyOkToEndSection(sectionName);
+
+ long sectionEnd = f.getFilePointer();
+ long sectionLength = sectionEnd - currentSectionStart;
+ f.seek(currentSectionStart - 4);
+ f.writeInt((int) sectionLength);
+ f.seek(sectionEnd);
+ currentSection = null;
+ currentSectionStart = -1;
+ }
+
+ private void verifyOkToBeginSection(String sectionName) {
+ if (sectionName.length() != 4)
+ throw new IllegalArgumentException(
+ "Section names must be 4 characters: `" + sectionName + "'.");
+ if (currentSection != null)
+ throw new IllegalStateException(
+ "Attempt to start `" + sectionName
+ + "' section before ending the previous section `"
+ + currentSection + "'.");
+ }
+
+ private void verifyOkToEndSection(String sectionName) {
+ if (sectionName.length() != 4)
+ throw new IllegalStateException("Invalid section name '" + sectionName
+ + "'; must be 4 characters.");
+ if (currentSection == null)
+ throw new IllegalStateException(
+ "Attempt to end section `" + sectionName
+ + "' when no section active.");
+ if (!sectionName.equals(currentSection))
+ throw new IllegalStateException(
+ "Attempt to end `" + sectionName
+ + "' section during active section `"
+ + currentSection + "'.");
+ }
+
+ private void writeSection(String sectionName, String contents) throws IOException {
+ verifyOkToBeginSection(sectionName);
+ f.writeBytes(sectionName);
+ f.writeInt(contents.length());
+ f.writeBytes(contents);
+ }
+
+ private void writeShortSection(String sectionName, int value) throws IOException {
+ verifyOkToBeginSection(sectionName);
+ f.writeBytes(sectionName);
+ f.writeInt(2);
+ f.writeShort(value);
+ }
+}
=== added file 'util/fonttool/src/org/gnu/grub/fonttool/Viewer.java'
--- util/fonttool/src/org/gnu/grub/fonttool/Viewer.java 1970-01-01 00:00:00 +0000
+++ util/fonttool/src/org/gnu/grub/fonttool/Viewer.java 2008-09-01 16:28:19 +0000
@@ -0,0 +1,135 @@
+package org.gnu.grub.fonttool;
+
+import java.io.File;
+import java.io.IOException;
+import javax.swing.*;
+
+/**
+ * Program to view fonts.
+ *
+ * Supports PFF2 and BDF format font files.
+ */
+public class Viewer {
+ public static void main(String[] args) {
+ if (args.length < 1) {
+ printUsageAndExit();
+ }
+
+ boolean verbose = false;
+ String format = null;
+ String filename = null;
+
+ try {
+ for (String arg : args) {
+ if (arg.startsWith("--")) {
+ String option;
+ String value;
+ int equalsPos = arg.indexOf('=');
+ if (equalsPos < 0) {
+ option = arg.substring(2);
+ value = null;
+ } else {
+ option = arg.substring(2, equalsPos);
+ value = arg.substring(equalsPos + 1);
+ }
+
+ if ("format".equals(option)) {
+ if (value == null)
+ throw new CommandLineException(option + " option requires a value.");
+ format = value;
+ } else if ("verbose".equals(option)) {
+ verbose = true;
+ }
+ } else {
+ if (filename == null) {
+ filename = arg;
+ } else {
+ throw new CommandLineException(
+ "Extra argument after filename: `" + arg + "'.");
+ }
+ }
+ }
+ if (filename == null) {
+ throw new CommandLineException("A filename must be specified.");
+ }
+ if (!(format == null || "bdf".equals(format) || "pff2".equals(format))) {
+ throw new CommandLineException("Invalid format: must be `bdf' or `pff2'.");
+ }
+ } catch (CommandLineException e) {
+ System.err.println("Error: " + e.getMessage());
+ System.exit(1);
+ }
+
+ if (!new File(filename).exists()) {
+ System.err.println("Error: File `" + filename + "' not found.");
+ System.exit(1);
+ }
+
+ if (format == null) {
+ // Autodetect the format.
+ if (PFF2Loader.isPFF2File(filename))
+ format = "pff2";
+ else if (BDFLoader.isBDFFile(filename))
+ format = "bdf";
+ else {
+ System.err.println("Error: Unable to detect font file format " +
+ "for `" + filename + "'.");
+ System.exit(1);
+ }
+ if (verbose)
+ System.out.println("Detected file format `" + format + "'.");
+ }
+
+ Font font = null;
+ try {
+ if ("bdf".equals(format)) {
+ if (verbose)
+ System.out.println("Loading BDF font.");
+ font = BDFLoader.loadFontFile(filename);
+ } else if ("pff2".equals(format)) {
+ if (verbose)
+ System.out.println("Loading PFF2 font.");
+ font = PFF2Loader.loadFontFile(filename);
+ } else {
+ System.err.println("Error: Bad format `" + format + "'");
+ System.exit(1);
+ }
+ } catch (IOException e) {
+ System.err.println("I/O error loading font: " + e);
+ e.printStackTrace();
+ System.exit(1);
+ }
+
+ if (font == null) {
+ System.err.println("Error: Font not loaded.");
+ System.exit(1);
+ }
+
+ if (verbose) {
+ System.out.println("Loaded font: '" + font.getStandardName() + "'");
+ }
+
+ ViewerFrame f = new ViewerFrame(font);
+ f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ f.setLocationRelativeTo(null); // Center the frame on screen.
+ f.setVisible(true);
+ }
+
+ private static class CommandLineException extends Exception {
+ public CommandLineException(String message) {
+ super(message);
+ }
+ }
+
+ private static void printUsageAndExit() {
+ System.err.println("GNU GRUB Font Viewer");
+ System.err.println();
+ System.err.println("Usage: Viewer [--format=FORMAT] FILE");
+ System.err.println(" --format=bdf The font is in BDF format.");
+ System.err.println(" --format=pff2 The font is in PFF2 format.");
+ System.err.println(" If no --format option is given, the type will be");
+ System.err.println(" automatically detected.");
+ System.err.println();
+ System.exit(1);
+ }
+}
=== added file 'util/fonttool/src/org/gnu/grub/fonttool/ViewerFrame.java'
--- util/fonttool/src/org/gnu/grub/fonttool/ViewerFrame.java 1970-01-01 00:00:00 +0000
+++ util/fonttool/src/org/gnu/grub/fonttool/ViewerFrame.java 2008-09-01 16:28:19 +0000
@@ -0,0 +1,61 @@
+package org.gnu.grub.fonttool;
+
+import java.awt.*;
+import javax.swing.*;
+import javax.swing.border.BevelBorder;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+public class ViewerFrame extends JFrame {
+ private final Font font;
+
+ private JLabel startingCodePointLabel;
+ private JSlider startingCodePointSlider;
+ private CharMatrix charMatrix;
+ private CharView charView;
+ private JLabel statusBar;
+
+ public ViewerFrame(Font font) {
+ this.font = font;
+
+ createComponents();
+ buildUI();
+ setSize(800, 600);
+ }
+
+ private void createComponents() {
+ startingCodePointLabel = new JLabel("Starting Code Point");
+ startingCodePointSlider = new JSlider(JSlider.HORIZONTAL, 0, 65535, 0);
+ startingCodePointSlider.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent e) {
+ charMatrix.setStartingChar(startingCodePointSlider.getValue());
+ }
+ });
+ charMatrix = new CharMatrix();
+ charMatrix.setCharFont(font);
+ charMatrix.setGlyphSelectionListener(new CharMatrix.GlyphSelectionListener() {
+ public void glyphSelected(int codePoint) {
+ charView.setGlyph(font.getGlyph(codePoint));
+ }
+ });
+ charView = new CharView();
+ charView.setZoom(3);
+ charView.setBorder(new BevelBorder(BevelBorder.RAISED));
+ statusBar = new JLabel();
+ }
+
+ private void buildUI() {
+ JComponent topPanel = Box.createVerticalBox();
+ topPanel.add(startingCodePointLabel);
+ topPanel.add(startingCodePointSlider);
+
+ JComponent charPanel = new JPanel(new GridLayout(2, 1));
+ charPanel.add(charMatrix);
+ charPanel.add(charView);
+
+ setLayout(new BorderLayout());
+ add(topPanel, BorderLayout.NORTH);
+ add(charPanel, BorderLayout.CENTER);
+ add(statusBar, BorderLayout.SOUTH);
+ }
+}
=== added directory 'util/fonttool/test'
=== added directory 'util/fonttool/test/org'
=== added directory 'util/fonttool/test/org/gnu'
=== added directory 'util/fonttool/test/org/gnu/grub'
=== added directory 'util/fonttool/test/org/gnu/grub/fonttool'
=== added file 'util/fonttool/test/org/gnu/grub/fonttool/TestGlyph.java'
--- util/fonttool/test/org/gnu/grub/fonttool/TestGlyph.java 1970-01-01 00:00:00 +0000
+++ util/fonttool/test/org/gnu/grub/fonttool/TestGlyph.java 2008-09-01 16:28:19 +0000
@@ -0,0 +1,110 @@
+package org.gnu.grub.fonttool;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TestGlyph {
+ @Test
+ public void testConstructor3x5() {
+ Glyph g = new Glyph(65, 3, 5, 0, 0, 0);
+
+ // Should be empty upon construction.
+ assertBitmapEquals("..." +
+ "..." +
+ "..." +
+ "..." +
+ "...",
+ g.getBitmap());
+
+ // Should have ceil(3 * 5 / 8) = ceil(15 / 8) = ceil(1 + 7/8) = 2 bytes.
+ Assert.assertEquals("Wrong bitmap array size.",
+ 2, g.getBitmap().length);
+ }
+
+ @Test
+ public void testConstructor5x5() {
+ Glyph g = new Glyph(65, 5, 5, 0, 0, 0);
+
+ // Should be empty upon construction.
+ assertBitmapEquals("....." +
+ "....." +
+ "....." +
+ "....." +
+ ".....",
+ g.getBitmap());
+
+ // Should have ceil(5 * 5 / 8) = ceil(25 / 8) = ceil(3 + 1/8) = 4 bytes.
+ Assert.assertEquals("Wrong bitmap array size.",
+ 4, g.getBitmap().length);
+ }
+
+ @Test
+ public void testSetPixel() {
+ Glyph g = new Glyph(65, 3, 5, 0, 0, 0);
+
+ g.setPixel(0, 0, true);
+ Assert.assertEquals(0x80, g.getBitmap()[0] & 0xFF);
+ Assert.assertEquals(0x00, g.getBitmap()[1] & 0xFF);
+ assertBitmapEquals("X.." +
+ "..." +
+ "..." +
+ "..." +
+ "...",
+ g.getBitmap());
+
+ g.setPixel(2, 1, true);
+ Assert.assertEquals(0x84, g.getBitmap()[0] & 0xFF);
+ Assert.assertEquals(0x00, g.getBitmap()[1] & 0xFF);
+ assertBitmapEquals("X.." +
+ "..X" +
+ "..." +
+ "..." +
+ "...",
+ g.getBitmap());
+ }
+
+ static void assertBitmapEquals(String expectedBitmap, byte[] actualBitmap) {
+ Assert.assertNotNull("expected bitmap null", expectedBitmap);
+ Assert.assertNotNull("actual bitmap null", actualBitmap);
+ StringBuilder errors = new StringBuilder();
+
+ int bit = 7;
+ int byteIndex = -1;
+ for (int bitIndex = 0; bitIndex < expectedBitmap.length(); bitIndex++) {
+ char c = expectedBitmap.charAt(bitIndex);
+ boolean expectedBit;
+ if (c == '.') {
+ expectedBit = false;
+ } else if (c == 'X') {
+ expectedBit = true;
+ } else {
+ throw new IllegalArgumentException("Bad character '" + c + "' in expected bitmap string");
+ }
+
+ byteIndex = bitIndex / 8;
+ byte b = actualBitmap[byteIndex];
+ boolean actualBit = (b & (1 << bit)) != 0;
+ if (actualBit != expectedBit) {
+ errors.append("[").append(bitIndex)
+ .append("] wrong (it is ")
+ .append(actualBit ? "1" : "0").append("). ");
+ }
+
+ if (bit == 0) {
+ bit = 7;
+ } else {
+ bit--;
+ }
+ }
+
+ if (byteIndex != expectedBitmap.length() / 8) {
+ errors.append("Last byte index wrong, was ")
+ .append(byteIndex).append(" but expected ")
+ .append(expectedBitmap.length() - 1);
+ }
+
+ if (errors.length() != 0) {
+ Assert.fail("Bitmap wrong: " + errors);
+ }
+ }
+}