org.newdawn.slick.tools.hiero.Kerning Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of slick2d-hiero Show documentation
Show all versions of slick2d-hiero Show documentation
Bitmap font editor for Slick 2D library
The newest version!
package org.newdawn.slick.tools.hiero;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
/**
* Reads a TTF font file and provides access to kerning information.
*
* Thanks to the Apache FOP project for their inspiring work!
*
* @author Nathan Sweet
*/
class Kerning {
private Map values = Collections.EMPTY_MAP;
private int size = -1;
private int kerningPairCount = -1;
private float scale;
private long bytePosition;
private long headOffset = -1;
private long kernOffset = -1;
/**
* @param input The data for the TTF font.
* @param size The font size to use to determine kerning pixel offsets.
* @throws IOException If the font could not be read.
*/
public void load (InputStream input, int size) throws IOException {
this.size = size;
if (input == null) throw new IllegalArgumentException("input cannot be null.");
readTableDirectory(input);
if (headOffset == -1) throw new IOException("HEAD table not found.");
if (kernOffset == -1) {
values = Collections.EMPTY_MAP;
return;
}
values = new HashMap(256);
if (headOffset < kernOffset) {
readHEAD(input);
readKERN(input);
} else {
readKERN(input);
readHEAD(input);
}
input.close();
for (Iterator entryIter = values.entrySet().iterator(); entryIter.hasNext();) {
Entry entry = (Entry)entryIter.next();
// Scale the offset values using the font size.
List valueList = (List)entry.getValue();
for (ListIterator valueIter = valueList.listIterator(); valueIter.hasNext();) {
int value = ((Integer)valueIter.next()).intValue();
int glyphCode = value & 0xffff;
int offset = value >> 16;
offset = Math.round(offset * scale);
if (offset == 0)
valueIter.remove();
else
valueIter.set(new Integer((offset << 16) | glyphCode));
}
if (valueList.isEmpty()) {
entryIter.remove();
} else {
// Replace ArrayList with int[].
int[] valueArray = new int[valueList.size()];
int i = 0;
for (Iterator valueIter = valueList.iterator(); valueIter.hasNext(); i++)
valueArray[i] = ((Integer)valueIter.next()).intValue();
entry.setValue(valueArray);
kerningPairCount += valueArray.length;
}
}
}
/**
* Returns the encoded kerning value for the specified glyph. The glyph code for a Unicode codepoint can be retrieved with
* {@link GlyphVector#getGlyphCode(int)}.
*/
public int[] getValues (int firstGlyphCode) {
return (int[])values.get(new Integer(firstGlyphCode));
}
public int getKerning (int[] values, int otherGlyphCode) {
int low = 0;
int high = values.length - 1;
while (low <= high) {
int midIndex = (low + high) >>> 1;
int value = values[midIndex];
int foundGlyphCode = value & 0xffff;
if (foundGlyphCode < otherGlyphCode)
low = midIndex + 1;
else if (foundGlyphCode > otherGlyphCode)
high = midIndex - 1;
else
return value >> 16;
}
return 0;
}
public int getCount () {
return kerningPairCount;
}
private void readTableDirectory (InputStream input) throws IOException {
skip(input, 4);
int tableCount = readUnsignedShort(input);
skip(input, 6);
byte[] tagBytes = new byte[4];
for (int i = 0; i < tableCount; i++) {
tagBytes[0] = readByte(input);
tagBytes[1] = readByte(input);
tagBytes[2] = readByte(input);
tagBytes[3] = readByte(input);
skip(input, 4);
long offset = readUnsignedLong(input);
skip(input, 4);
String tag = new String(tagBytes, "ISO-8859-1");
if (tag.equals("head")) {
headOffset = offset;
if (kernOffset != -1) break;
} else if (tag.equals("kern")) {
kernOffset = offset;
if (headOffset != -1) break;
}
}
}
private void readHEAD (InputStream input) throws IOException {
seek(input, headOffset + 2 * 4 + 2 * 4 + 2);
int unitsPerEm = readUnsignedShort(input);
scale = (float)size / unitsPerEm;
}
private void readKERN (InputStream input) throws IOException {
seek(input, kernOffset + 2);
for (int subTableCount = readUnsignedShort(input); subTableCount > 0; subTableCount--) {
skip(input, 2 * 2);
int tupleIndex = readUnsignedShort(input);
if (!((tupleIndex & 1) != 0) || (tupleIndex & 2) != 0 || (tupleIndex & 4) != 0) return;
if (tupleIndex >> 8 != 0) continue;
int kerningCount = readUnsignedShort(input);
skip(input, 3 * 2);
while (kerningCount-- > 0) {
int firstGlyphCode = readUnsignedShort(input);
int secondGlyphCode = readUnsignedShort(input);
int offset = readShort(input);
int value = (offset << 16) | secondGlyphCode;
List firstGlyphValues = (List)values.get(new Integer(firstGlyphCode));
if (firstGlyphValues == null) {
firstGlyphValues = new ArrayList(256);
values.put(new Integer(firstGlyphCode), firstGlyphValues);
}
firstGlyphValues.add(new Integer(value));
}
}
}
private int readUnsignedByte (InputStream input) throws IOException {
bytePosition++;
int b = input.read();
if (b == -1) throw new EOFException("Unexpected end of file.");
return b;
}
private byte readByte (InputStream input) throws IOException {
return (byte)readUnsignedByte(input);
}
private int readUnsignedShort (InputStream input) throws IOException {
return (readUnsignedByte(input) << 8) + readUnsignedByte(input);
}
private short readShort (InputStream input) throws IOException {
return (short)readUnsignedShort(input);
}
private long readUnsignedLong (InputStream input) throws IOException {
long value = readUnsignedByte(input);
value = (value << 8) + readUnsignedByte(input);
value = (value << 8) + readUnsignedByte(input);
value = (value << 8) + readUnsignedByte(input);
return value;
}
private void skip (InputStream input, long skip) throws IOException {
while (skip > 0) {
long skipped = input.skip(skip);
if (skipped <= 0) break;
bytePosition += skipped;
skip -= skipped;
}
}
private void seek (InputStream input, long position) throws IOException {
skip(input, position - bytePosition);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy