// Copyright 2001-2006, FreeHEP.
package org.freehep.graphicsio.font.truetype;
import com.google.code.appengine.awt.Rectangle;
import com.google.code.appengine.awt.geom.AffineTransform;
import com.google.code.appengine.awt.geom.GeneralPath;
import java.io.IOException;
/**
* GLYPH Table.
*
* @author Simon Fischer
* @version $Id: TTFGlyfTable.java 8584 2006-08-10 23:06:37Z duns $
*/
public class TTFGlyfTable extends TTFVersionTable {
/**
* If this variable is set to false then the glyphs will not be read until
* they are retrieved with <tt>getGlyph(int)</tt>.
*/
private static final boolean READ_GLYPHS = false;
public abstract class Glyph {
public int xMin, yMin, xMax, yMax;
public abstract String getType();
public abstract GeneralPath getShape();
public void read() throws IOException {
xMin = ttf.readFWord();
yMin = ttf.readFWord();
xMax = ttf.readFWord();
yMax = ttf.readFWord();
}
public Rectangle getBBox() {
return new Rectangle(xMin, yMin, xMax - xMin, yMax - yMin);
}
public String toString() {
return "[" + getType() + "] (" + xMin + "," + yMin + "):(" + xMax
+ "," + yMax + ")";
}
public String toDetailedString() {
return toString();
}
}
// --------------------------------------------------------------------------------
public class SimpleGlyph extends Glyph {
private static final int ON_CURVE = 0;
private static final int X_SHORT = 1;
private static final int Y_SHORT = 2;
private static final int REPEAT_FLAG = 3;
private static final int X_SAME = 4;
private static final int Y_SAME = 5;
private static final int X_POSITIVE = 4;
private static final int Y_POSITIVE = 5;
public int numberOfContours;
public int[] endPtsOfContours;
public int[] instructions;
public int[] flags;
public int[] xCoordinates, yCoordinates;
public boolean[] onCurve;
public GeneralPath shape;
public SimpleGlyph(int numberOfContours) {
this.numberOfContours = numberOfContours;
this.endPtsOfContours = new int[numberOfContours];
}
public String getType() {
return "Simple Glyph";
}
public void read() throws IOException {
super.read();
for (int i = 0; i < endPtsOfContours.length; i++)
endPtsOfContours[i] = ttf.readUShort();
instructions = new int[ttf.readUShort()];
for (int i = 0; i < instructions.length; i++)
instructions[i] = ttf.readByte();
int numberOfPoints = endPtsOfContours[endPtsOfContours.length - 1] + 1;
flags = new int[numberOfPoints];
xCoordinates = new int[numberOfPoints];
yCoordinates = new int[numberOfPoints];
onCurve = new boolean[numberOfPoints];
int repeatCount = 0;
int repeatFlag = 0;
for (int i = 0; i < numberOfPoints; i++) {
if (repeatCount > 0) {
flags[i] = repeatFlag;
repeatCount--;
} else {
flags[i] = ttf.readRawByte();
if (TTFInput.flagBit(flags[i], REPEAT_FLAG)) {
repeatCount = ttf.readByte();
repeatFlag = flags[i];
}
}
TTFInput.checkZeroBit(flags[i], 6, "flags");
TTFInput.checkZeroBit(flags[i], 7, "flags");
onCurve[i] = TTFInput.flagBit(flags[i], ON_CURVE);
}
int last = 0;
for (int i = 0; i < numberOfPoints; i++) {
if (TTFInput.flagBit(flags[i], X_SHORT)) {
if (TTFInput.flagBit(flags[i], X_POSITIVE)) {
last = xCoordinates[i] = last + ttf.readByte();
} else {
last = xCoordinates[i] = last - ttf.readByte();
}
} else {
if (TTFInput.flagBit(flags[i], X_SAME)) {
last = xCoordinates[i] = last;
} else {
last = xCoordinates[i] = last + ttf.readShort();
}
}
}
last = 0;
for (int i = 0; i < numberOfPoints; i++) {
if (TTFInput.flagBit(flags[i], Y_SHORT)) {
if (TTFInput.flagBit(flags[i], Y_POSITIVE)) {
last = yCoordinates[i] = last + ttf.readByte();
} else {
last = yCoordinates[i] = last - ttf.readByte();
}
} else {
if (TTFInput.flagBit(flags[i], Y_SAME)) {
last = yCoordinates[i] = last;
} else {
last = yCoordinates[i] = last + ttf.readShort();
}
}
}
}
public String toString() {
String str = super.toString() + ", " + numberOfContours
+ " contours, endPts={";
for (int i = 0; i < numberOfContours; i++)
str += (i == 0 ? "" : ",") + endPtsOfContours[i];
str += "}, " + instructions.length + " instructions";
return str;
}
public String toDetailedString() {
String str = toString() + "\n instructions = {";
for (int i = 0; i < instructions.length; i++) {
str += Integer.toHexString(instructions[i]) + " ";
}
return str + "}";
}
public GeneralPath getShape() {
if (shape != null) {
return shape;
}
shape = new GeneralPath(GeneralPath.WIND_NON_ZERO);
int p = 0;
for (int i = 0; i < endPtsOfContours.length; i++) {
int startIndex = p++;
shape.moveTo(xCoordinates[startIndex], yCoordinates[startIndex]);
boolean lastOnCurve = true;
while (p <= endPtsOfContours[i]) {
if (onCurve[p]) {
if (lastOnCurve) {
shape.lineTo(xCoordinates[p], yCoordinates[p]);
} else {
shape.quadTo(xCoordinates[p - 1], yCoordinates[p - 1],
xCoordinates[p], yCoordinates[p]);
}
lastOnCurve = true;
} else {
if (!lastOnCurve) {
int x1 = xCoordinates[p - 1];
int y1 = yCoordinates[p - 1];
int x2 = (int)((x1 + xCoordinates[p])/ 2.0);
int y2 = (int)((y1 + yCoordinates[p])/ 2.0);
shape.quadTo(x1, y1, x2, y2);
}
lastOnCurve = false;
}
p++;
}
if (!onCurve[p - 1]) {
shape.quadTo(xCoordinates[p - 1], yCoordinates[p - 1],
xCoordinates[startIndex], yCoordinates[startIndex]);
} else if ((xCoordinates[p - 1] != xCoordinates[startIndex]) ||
(yCoordinates[p - 1] != yCoordinates[startIndex])) {
shape.closePath();
}
}
return shape;
}
}
// --------------------------------------------------------------------------------
public class CompositeGlyph extends Glyph {
private static final int ARGS_WORDS = 0;
private static final int ARGS_XY = 1;
private static final int SCALE = 3;
private static final int XY_SCALE = 6;
private static final int TWO_BY_TWO = 7;
private static final int MORE_COMPONENTS = 5;
private GeneralPath shape;
private int noComponents;
public String getType() {
return "Composite Glyph";
}
public GeneralPath getShape() {
return shape;
}
public void read() throws IOException {
super.read();
shape = new GeneralPath();
noComponents = 0;
boolean more = true;
while (more) {
noComponents++;
ttf.readUShortFlags();
more = ttf.flagBit(MORE_COMPONENTS);
int glyphIndex = ttf.readUShort();
int arg1, arg2;
if (ttf.flagBit(ARGS_WORDS)) {
arg1 = ttf.readShort();
arg2 = ttf.readShort();
} else {
arg1 = ttf.readChar();
arg2 = ttf.readChar();
}
AffineTransform t = new AffineTransform();
if (ttf.flagBit(ARGS_XY)) {
t.translate(arg1, arg2);
} else {
System.err
.println("TTFGlyfTable: ARGS_ARE_POINTS not implemented.");
}
if (ttf.flagBit(SCALE)) {
double scale = ttf.readF2Dot14();
t.scale(scale, scale);
} else if (ttf.flagBit(XY_SCALE)) {
double scaleX = ttf.readF2Dot14();
double scaleY = ttf.readF2Dot14();
t.scale(scaleX, scaleY);
} else if (ttf.flagBit(TWO_BY_TWO)) {
System.err
.println("TTFGlyfTable: WE_HAVE_A_TWO_BY_TWO not implemented.");
}
GeneralPath appendGlyph = (GeneralPath) getGlyph(glyphIndex)
.getShape().clone();
appendGlyph.transform(t);
shape.append(appendGlyph, false);
}
}
public String toString() {
return super.toString() + ", " + noComponents + " components";
}
}
// --------------------------------------------------------------------------------
public Glyph[] glyphs;
private long[] offsets;
public String getTag() {
return "glyf";
}
public void readTable() throws IOException {
glyphs = new Glyph[((TTFMaxPTable) getTable("maxp")).numGlyphs];
offsets = ((TTFLocaTable) getTable("loca")).offset;
if (READ_GLYPHS) {
for (int i = 0; i < glyphs.length; i++) {
if ((i > 0) && (offsets[i - 1] == offsets[i])) {
glyphs[i] = glyphs[i - 1];
} else {
try {
getGlyph(i);
} catch (IOException e) {
System.err.println("While reading glyph #" + i
+ " (offset " + offsets[i] + "):");
e.printStackTrace();
}
}
}
}
}
public Glyph getGlyph(int i) throws IOException {
if (glyphs[i] != null) {
return glyphs[i];
} else {
ttf.pushPos();
ttf.seek(offsets[i]);
int numberOfContours = ttf.readShort();
if (numberOfContours >= 0)
glyphs[i] = new SimpleGlyph(numberOfContours);
else
glyphs[i] = new CompositeGlyph();
glyphs[i].read();
// System.out.println(i+": "+offsets[i]+"-"+ttf.getPointer());
ttf.popPos();
return glyphs[i];
}
}
public String toString() {
String str = super.toString();
for (int i = 0; i < glyphs.length; i++)
str += "\n #" + i + ": " + glyphs[i];
return str;
}
}