/*
* TTFDecoder.java
* Transform
*
* Copyright (c) 2009-2010 Flagstone Software Ltd. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Flagstone Software Ltd. nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package com.flagstone.transform.util.font;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.DataFormatException;
import com.flagstone.transform.Constants;
import com.flagstone.transform.coder.BigDecoder;
import com.flagstone.transform.coder.Coder;
import com.flagstone.transform.datatype.Bounds;
import com.flagstone.transform.datatype.CoordTransform;
import com.flagstone.transform.font.CharacterFormat;
import com.flagstone.transform.shape.Shape;
import com.flagstone.transform.shape.ShapeRecord;
import com.flagstone.transform.util.shape.Canvas;
/**
* TTFDecoder decodes TrueType or OpenType Fonts so they can be used in a
* Flash file.
*/
@SuppressWarnings({"PMD.ExcessiveClassLength",
"PMD.TooManyFields",
"PMD.TooManyMethods",
"PMD.ExcessiveImports",
"PMD.CyclomaticComplexity",
"PMD.NPathComplexity",
"PMD.ExcessiveMethodLength",
"PMD.NcssMethodCount" })
public final class TTFDecoder implements FontProvider, FontDecoder {
/**
* TableEntry is used to load the encoded TrueType tables so they can
* be decoded.
*/
private static final class TableEntry implements Comparable<TableEntry> {
/** The table name/signature. */
private transient int type;
/** The offset to the start of the table. */
private transient int offset;
/** The number of bytes in the table. */
private transient int length;
/** The encoded table data. */
private transient byte[] data;
/** {@inheritDoc} */
@Override
public int compareTo(final TableEntry obj) {
int result;
if (offset < obj.offset) {
result = -1;
} else if (offset == obj.offset) {
result = 0;
} else {
result = 1;
}
return result;
}
/** {@inheritDoc} */
@Override
public boolean equals(final Object object) {
boolean result;
TableEntry entry;
if (object == null) {
result = false;
} else if (object == this) {
result = true;
} else if (object instanceof TableEntry) {
entry = (TableEntry) object;
result = offset == entry.offset;
} else {
result = false;
}
return result;
}
/** {@inheritDoc} */
@Override
public int hashCode() {
return offset * Constants.PRIME;
}
/**
* Set the table data.
* @param bytes the contents of the table.
*/
public void setData(final byte[] bytes) {
data = Arrays.copyOf(bytes, bytes.length);
}
/**
* Get the table contents.
* @return the array of bytes containing the encoded table.
*/
public byte[] getData() {
return Arrays.copyOf(data, data.length);
}
}
/**
* Use to store entries from the NAME table.
*/
private static final class NameEntry {
/** platform identifier. */
private int platform;
/** character encoding identifier. */
private int encoding;
/** language identifier. */
private int language;
/** name identifier. */
private int name;
/** length of the name string. */
private int length;
/** offset from the start of the table where the string is located. */
private int offset;
/** Creates a new NameEntry. */
protected NameEntry() {
// NOPMD
}
}
/** Number of bits to shift to convert bytes to bits. */
private static final int BYTES_TO_BITS = 3;
/** The number of bits to shift a byte to sign extend to 32-bits. */
private static final int SIGN_EXTEND = 24;
/** The name of the OS/2 table. */
private static final int OS_2 = 0x4F532F32;
/** The name of the head table. */
private static final int HEAD = 0x68656164;
/** The name of the hhea table. */
private static final int HHEA = 0x68686561;
/** The name of the maxp table. */
private static final int MAXP = 0x6D617870;
/** The name of the loca table. */
private static final int LOCA = 0x6C6F6361;
/** The name of the cmap table. */
private static final int CMAP = 0x636D6170;
/** The name of the hmtx table. */
private static final int HMTX = 0x686D7478;
/** The name of the name table. */
private static final int NAME = 0x6E616D65;
/** The name of the glyf table. */
private static final int GLYF = 0x676C7966;
/** Indicates the offset to the glyph is encoded in 16-bits. */
private static final int ITLF_SHORT = 0;
// private static final int ITLF_LONG = 1;
// private static final int WEIGHT_THIN = 100;
// private static final int WEIGHT_EXTRALIGHT = 200;
// private static final int WEIGHT_LIGHT = 300;
// private static final int WEIGHT_NORMAL = 400;
// private static final int WEIGHT_MEDIUM = 500;
// private static final int WEIGHT_SEMIBOLD = 600;
/** Code identifying a font is bold. */
private static final int WEIGHT_BOLD = 700;
// private static final int WEIGHT_EXTRABOLD = 800;
// private static final int WEIGHT_BLACK = 900;
/**
* Mask for the field that identifies whether a point is located on the
* outline of a glyph.
*/
private static final int ON_CURVE = 0x01;
/**
* Mask for the field that identifies whether the x-coordinate of a point
* encoded in 16-bits.
*/
private static final int X_SHORT = 0x02;
/**
* Mask for the field that identifies whether the y-coordinate of a point
* encoded in 16-bits.
*/
private static final int Y_SHORT = 0x04;
/**
* Mask for the field that identifies whether the coordinate of a point
* is repeated.
*/
private static final int REPEAT_FLAG = 0x08;
/**
* Mask for the field that identifies whether the x-coordinate of a point
* is unchanged.
*/
private static final int X_SAME = 0x10;
/**
* Mask for the field that identifies whether the y-coordinate of a point
* is unchanged.
*/
private static final int Y_SAME = 0x20;
/**
* Mask for the field that identifies whether the value for the relative
* change in the x-coordinate of a point is added to the previous value.
*/
private static final int X_POSITIVE = 0x10;
/**
* Mask for the field that identifies whether the value for the relative
* change in the y-coordinate of a point is added to the previous value.
*/
private static final int Y_POSITIVE = 0x20;
/** The coordinates for the encoded glyph is 32-bits. */
private static final int ARGS_ARE_WORDS = 0x01;
/** X and Y coordinates are encoded. */
private static final int ARGS_ARE_XY = 0x02;
/** The font contains scaling information. */
private static final int HAVE_SCALE = 0x08;
/** Scaling for both the x and y axes are included. */
private static final int HAVE_XYSCALE = 0x40;
/** Scaling for both the x and y axes includes an offset. */
private static final int HAVE_2X2 = 0x80;
/** The outline of the glyph has more points to be decoded. */
private static final int HAS_MORE = 0x10;
/** The name of the font. */
private transient String name;
/** Indicates whether the font weight is bold. */
private transient boolean bold;
/** Indicates whether the font is italicised. */
private transient boolean italic;
/** The encoding used for characters. */
private transient CharacterFormat encoding;
/** The ascent of the font. */
private transient float ascent;
/** The descent of the font. */
private transient float descent;
/** The leading of the font. */
private transient float leading;
/** Table mapping character code to glyphs. */
private transient int[] charToGlyph;
/** Table mapping glyph to character codes. */
private transient int[] glyphToChar;
/** Glyphs. */
private transient TrueTypeGlyph[] glyphTable;
/** The number of glyphs defined in the font. */
private transient int glyphCount;
/** The index of the glyph that represents unsupported characters. */
private transient int missingGlyph;
/** The highest character code represented in the font. */
private transient char maxChar;
/** The amount to scale coordinates so the font maps to the EM-SQUARE. */
private transient int scale = 1;
/** The number of entries in the table of advances for each glyph. */
private transient int metrics;
/** The size of each entry in the glyph table, either 16 or 32 bits. */
private transient int glyphOffset;
/** The offsets in bytes to each glyph in the GLYF table. */
private transient int[] offsets;
/** Directory of tables encoded in the font. */
private final transient Map<Integer, TableEntry> table
= new LinkedHashMap<Integer, TableEntry>();
/** Table of fonts decoded from the font definition. */
private final transient List<Font>fonts = new ArrayList<Font>();
/** {@inheritDoc} */
@Override
public FontDecoder newDecoder() {
return new TTFDecoder();
}
/** {@inheritDoc} */
@Override
public void read(final File file) throws IOException, DataFormatException {
final FileInputStream stream = new FileInputStream(file);
try {
read(stream);
} finally {
if (stream != null) {
stream.close();
}
}
}
/** {@inheritDoc} */
@Override
public void read(final URL url) throws IOException, DataFormatException {
final URLConnection connection = url.openConnection();
final int contentLength = connection.getContentLength();
if (contentLength < 0) {
throw new FileNotFoundException(url.getFile());
}
final InputStream stream = connection.getInputStream();
try {
read(stream);
} finally {
if (stream != null) {
stream.close();
}
}
}
/** {@inheritDoc} */
@Override
public List<Font> getFonts() {
return fonts;
}
/**
* Read a font from an input stream.
* @param stream the stream containing the font data.
* @throws IOException if there is an error reading the font data.
*/
public void read(final InputStream stream) throws IOException {
loadTables(stream);
decodeTables();
final Font font = new Font();
font.setFace(new FontFace(name, bold, italic));
font.setEncoding(encoding);
font.setAscent((int) ascent);
font.setDescent((int) descent);
font.setLeading((int) leading);
font.setNumberOfGlyphs(glyphCount);
font.setMissingGlyph(missingGlyph);
font.setHighestChar(maxChar);
for (int i = 0; i < glyphCount; i++) {
font.addGlyph((char) glyphToChar[i], glyphTable[i]);
}
fonts.add(font);
}
/**
* Load the tables from the TrueType table directory.
* @param stream the InputStream containing the font data.
* @throws IOException if there is an error loading the table data.
*/
private void loadTables(final InputStream stream) throws IOException {
final BigDecoder coder = new BigDecoder(stream);
coder.mark();
/* float version = */coder.readInt();
final int tableCount = coder.readUnsignedShort();
/* int searchRange = */coder.readUnsignedShort();
/* int entrySelector = */coder.readUnsignedShort();
/* int rangeShift = */coder.readUnsignedShort();
TableEntry[] directory = new TableEntry[tableCount];
for (int i = 0; i < tableCount; i++) {
directory[i] = new TableEntry();
directory[i].type = coder.readInt();
/* checksum */ coder.readInt();
directory[i].offset = coder.readInt();
directory[i].length = coder.readInt();
}
Arrays.sort(directory);
for (TableEntry entry : directory) {
coder.skip(entry.offset - coder.bytesRead());
entry.setData(coder.readBytes(new byte[entry.length]));
table.put(entry.type, entry);
}
}
/**
* Decode the data from the loaded tables. The order is important since
* some tables (maxp) contains values such as the number of glyphs etc.
* that are used to size the table used to decode the glyphs.
* @throws IOException if there is an error decoding the data.
*/
private void decodeTables() throws IOException {
decodeMAXP(table.get(MAXP));
decodeOS2(table.get(OS_2));
decodeHEAD(table.get(HEAD));
decodeHHEA(table.get(HHEA));
decodeNAME(table.get(NAME));
decodeLOCA(table.get(LOCA));
decodeGlyphs(table.get(GLYF));
decodeHMTX(table.get(HMTX));
decodeCMAP(table.get(CMAP));
}
/**
* Decode the HEAD table.
*
* @param entry the bleEntry containing the encoded HEAD table data.
* @throws IOException if an error occurs while decoding the table data.
*/
private void decodeHEAD(final TableEntry entry) throws IOException {
final byte[] data = entry.getData();
final ByteArrayInputStream stream = new ByteArrayInputStream(data);
final BigDecoder coder = new BigDecoder(stream, data.length);
final byte[] date = new byte[8];
coder.readInt(); // table version fixed 16
coder.readInt(); // font version fixed 16
coder.readInt(); // checksum adjustment
coder.readInt(); // magic number
coder.readUnsignedShort(); // See following comments
// bit15: baseline at y=0
// bit14: side bearing at x=0;
// bit13: instructions depend on point size
// bit12: force ppem to integer values
// bit11: instructions may alter advance
// bits 10-0: unused.
scale = coder.readUnsignedShort() / 1024; // units per em
if (scale == 0) {
scale = 1;
}
coder.readBytes(date); // number of seconds since midnight, Jan 01 1904
coder.readBytes(date); // number of seconds since midnight, Jan 01 1904
coder.readShort(); // xMin for all glyph bounding boxes
coder.readShort(); // yMin for all glyph bounding boxes
coder.readShort(); // xMax for all glyph bounding boxes
coder.readShort(); // yMax for all glyph bounding boxes
/*
* Next two byte define font appearance on Macs, values are specified in
* the OS/2 table
*/
final int flags = coder.readUnsignedShort();
bold = (flags & Coder.BIT15) != 0;
italic = (flags & Coder.BIT10) != 0;
coder.readUnsignedShort(); // smallest readable size in pixels
coder.readShort(); // font direction hint
glyphOffset = coder.readShort();
coder.readShort(); // glyph data format
}
/**
* Decode the HHEA table.
*
* @param entry the bleEntry containing the encoded HHEA table data.
* @throws IOException if an error occurs while decoding the table data.
*/
private void decodeHHEA(final TableEntry entry) throws IOException {
final byte[] data = entry.getData();
final ByteArrayInputStream stream = new ByteArrayInputStream(data);
final BigDecoder coder = new BigDecoder(stream, data.length);
coder.readInt(); // table version, fixed 16
ascent = coder.readShort() / (float) scale;
descent = -(coder.readShort() / (float) scale);
leading = coder.readShort() / (float) scale;
coder.readUnsignedShort(); // maximum advance in the htmx table
coder.readShort(); // minimum left side bearing in the htmx table
coder.readShort(); // minimum right side bearing in the htmx table
coder.readShort(); // maximum extent
coder.readShort(); // caret slope rise
coder.readShort(); // caret slope run
coder.readShort(); // caret offset
coder.readUnsignedShort(); // reserved
coder.readUnsignedShort(); // reserved
coder.readUnsignedShort(); // reserved
coder.readUnsignedShort(); // reserved
coder.readShort(); // metric data format
metrics = coder.readUnsignedShort();
}
/**
* Decode the OS/2 table.
*
* @param entry the bleEntry containing the encoded OS/2 table data.
* @throws IOException if an error occurs while decoding the table data.
*/
private void decodeOS2(final TableEntry entry) throws IOException {
final byte[] data = entry.getData();
final ByteArrayInputStream stream = new ByteArrayInputStream(data);
final BigDecoder coder = new BigDecoder(stream, data.length);
final byte[] panose = new byte[10];
final int[] unicodeRange = new int[4];
final byte[] vendor = new byte[4];
final int version = coder.readUnsignedShort(); // version
coder.readShort(); // average character width
final int weight = coder.readUnsignedShort();
if (weight == WEIGHT_BOLD) {
bold = true;
}
coder.readUnsignedShort(); // width class
coder.readUnsignedShort(); // embedding licence
coder.readShort(); // subscript x size
coder.readShort(); // subscript y size
coder.readShort(); // subscript x offset
coder.readShort(); // subscript y offset
coder.readShort(); // superscript x size
coder.readShort(); // superscript y size
coder.readShort(); // superscript x offset
coder.readShort(); // superscript y offset
coder.readShort(); // width of strikeout stroke
coder.readShort(); // strikeout stroke position
coder.readShort(); // font family class
coder.readBytes(panose);
for (int i = 0; i < 4; i++) {
unicodeRange[i] = coder.readInt();
}
coder.readBytes(vendor); // font vendor identification
final int flags = coder.readUnsignedShort();
italic = (flags & Coder.BIT15) != 0;
bold = (flags & Coder.BIT10) != 0;
coder.readUnsignedShort(); // first unicode character code
coder.readUnsignedShort(); // last unicode character code
ascent = coder.readUnsignedShort() / (float) scale;
descent = -(coder.readUnsignedShort() / (float) scale);
leading = coder.readUnsignedShort() / (float) scale;
coder.readUnsignedShort(); // ascent in Windows
coder.readUnsignedShort(); // descent in Windows
if (version > 0) {
coder.readInt(); // code page range
coder.readInt(); // code page range
if (version > 1) {
coder.readShort(); // height
coder.readShort(); // Capitals height
missingGlyph = coder.readUnsignedShort();
coder.readUnsignedShort(); // break character
coder.readUnsignedShort(); // maximum context
}
}
}
/**
* Decode the NAME table.
*
* @param entry the bleEntry containing the encoded NAME table data.
* @throws IOException if an error occurs while decoding the table data.
*/
private void decodeNAME(final TableEntry entry) throws IOException {
final byte[] data = entry.getData();
final ByteArrayInputStream stream = new ByteArrayInputStream(data);
final BigDecoder coder = new BigDecoder(stream, data.length);
/* final int format = */ coder.readUnsignedShort();
final int names = coder.readUnsignedShort();
final int tableOffset = coder.readUnsignedShort();
NameEntry[] nameTable = new NameEntry[names];
for (int i = 0; i < names; i++) {
nameTable[i] = new NameEntry();
nameTable[i].platform = coder.readUnsignedShort();
nameTable[i].encoding = coder.readUnsignedShort();
nameTable[i].language = coder.readUnsignedShort();
nameTable[i].name = coder.readUnsignedShort();
nameTable[i].length = coder.readUnsignedShort();
nameTable[i].offset = coder.readUnsignedShort();
}
for (int i = 0; i < names; i++) {
coder.reset();
coder.skip(tableOffset + nameTable[i].offset);
final byte[] bytes = new byte[nameTable[i].length];
coder.readBytes(bytes);
String nameEncoding = "UTF-8";
if (nameTable[i].platform == 0) {
// Unicode
nameEncoding = "UTF-16";
} else if (nameTable[i].platform == 1) {
// Macintosh
if ((nameTable[i].encoding == 0)
&& (nameTable[i].language == 0)) {
nameEncoding = "ISO8859-1";
}
} else if (nameTable[i].platform == 3) {
// Microsoft
switch (nameTable[i].encoding) {
case 1:
nameEncoding = "UTF-16";
break;
case 2:
nameEncoding = "SJIS";
break;
case 4:
nameEncoding = "Big5";
break;
default:
nameEncoding = "UTF-8";
break;
}
}
try {
if (nameTable[i].name == 1) {
name = new String(bytes, nameEncoding);
}
} catch (final UnsupportedEncodingException e) {
name = new String(bytes);
}
}
}
/**
* Decode the MAXP table.
*
* @param entry the bleEntry containing the encoded MAXP table data.
* @throws IOException if an error occurs while decoding the table data.
*/
private void decodeMAXP(final TableEntry entry) throws IOException {
final byte[] data = entry.getData();
final ByteArrayInputStream stream = new ByteArrayInputStream(data);
final BigDecoder coder = new BigDecoder(stream, data.length);
final float version = coder.readInt() / Coder.SCALE_16;
glyphCount = coder.readUnsignedShort();
glyphTable = new TrueTypeGlyph[glyphCount];
glyphToChar = new int[glyphCount];
if (version == 1.0f) {
coder.readUnsignedShort(); // max no. of points in a simple glyph
coder.readUnsignedShort(); // max no. of contours in a simple glyph
coder.readUnsignedShort(); // max no. of points in a composite glyph
coder.readUnsignedShort(); // max no. of composite glyph contours
coder.readUnsignedShort(); // max no. of zones
coder.readUnsignedShort(); // max no. of point in Z0
coder.readUnsignedShort(); // number of storage area locations
coder.readUnsignedShort(); // max no. of FDEFs
coder.readUnsignedShort(); // max no. of IDEFs
coder.readUnsignedShort(); // maximum stack depth
coder.readUnsignedShort(); // max byte count for glyph instructions
coder.readUnsignedShort(); // max no. of composite glyphs components
coder.readUnsignedShort(); // max levels of recursion
}
}
/**
* Decode the HMTX table.
*
* @param entry the bleEntry containing the encoded HMTX table data.
* @throws IOException if an error occurs while decoding the table data.
*/
private void decodeHMTX(final TableEntry entry) throws IOException {
final byte[] data = entry.getData();
final ByteArrayInputStream stream = new ByteArrayInputStream(data);
final BigDecoder coder = new BigDecoder(stream, data.length);
int index = 0;
for (index = 0; index < metrics; index++) {
glyphTable[index].setAdvance((coder.readUnsignedShort() / scale));
coder.readShort(); // left side bearing
}
final int advance = glyphTable[index - 1].getAdvance();
while (index < glyphCount) {
glyphTable[index++].setAdvance(advance);
}
while (index < glyphCount) {
coder.readShort();
index++;
}
}
/**
* Decode the CMAP table.
*
* @param entry the bleEntry containing the encoded CMAP table data.
* @throws IOException if an error occurs while decoding the table data.
*/
private void decodeCMAP(final TableEntry entry) throws IOException {
final byte[] data = entry.getData();
final ByteArrayInputStream stream = new ByteArrayInputStream(data);
final BigDecoder coder = new BigDecoder(stream, data.length);
/* final int version = */ coder.readUnsignedShort();
final int numberOfTables = coder.readUnsignedShort();
int platformId;
int encodingId;
int offset = 0;
int format = 0;
for (int tableCount = 0; tableCount < numberOfTables; tableCount++) {
platformId = coder.readUnsignedShort();
encodingId = coder.readUnsignedShort();
offset = coder.readInt();
coder.mark();
if (platformId == 0) {
// Unicode
encoding = CharacterFormat.UCS2;
} else if (platformId == 1) {
// Macintosh
if (encodingId == 1) {
encoding = CharacterFormat.SJIS;
} else {
encoding = CharacterFormat.ANSI;
}
} else if (platformId == 3) {
// Microsoft
if (encodingId == 1) {
encoding = CharacterFormat.UCS2;
} else if (encodingId == 2) {
encoding = CharacterFormat.SJIS;
} else {
encoding = CharacterFormat.ANSI;
}
}
coder.move(offset);
format = coder.readUnsignedShort();
/* length = */ coder.readUnsignedShort();
/* language = */ coder.readUnsignedShort();
if (format == 0) {
decodeSimpleCMAP(coder);
} else if (format == 4) {
decodeRangeCMAP(coder);
} else {
throw new IOException();
}
coder.reset();
}
encoding = CharacterFormat.SJIS;
}
/**
* Decode a simple character table.
*
* @param coder a BigDecoder containing data for the table.
* @throws IOException if an error occurs while decoding the table data.
*/
private void decodeSimpleCMAP(final BigDecoder coder) throws IOException {
charToGlyph = new int[256];
maxChar = 255;
for (int index = 0; index < 256; index++) {
charToGlyph[index] = coder.readByte();
glyphToChar[charToGlyph[index]] = index;
}
}
/**
* Decode a range (type 4) character table.
*
* @param coder a BigDecoder containing data for the table.
* @throws IOException if an error occurs while decoding the table data.
*/
private void decodeRangeCMAP(final BigDecoder coder) throws IOException {
final int segmentCount = coder.readUnsignedShort() / 2;
coder.readUnsignedShort(); // search range
coder.readUnsignedShort(); // entry selector
coder.readUnsignedShort(); // range shift
final int[] startCount = new int[segmentCount];
final int[] endCount = new int[segmentCount];
final int[] delta = new int[segmentCount];
final int[] range = new int[segmentCount];
final int[] rangeAdr = new int[segmentCount];
for (int index = 0; index < segmentCount; index++) {
endCount[index] = coder.readUnsignedShort();
if (endCount[index] > maxChar) {
maxChar = (char) endCount[index];
}
}
charToGlyph = new int[maxChar + 1];
coder.readUnsignedShort(); // reserved padding
for (int index = 0; index < segmentCount; index++) {
startCount[index] = coder.readUnsignedShort();
}
for (int index = 0; index < segmentCount; index++) {
delta[index] = coder.readShort();
}
for (int index = 0; index < segmentCount; index++) {
rangeAdr[index] = coder.mark();
range[index] = coder.readShort();
coder.unmark();
}
int glyphIndex = 0;
int location = 0;
for (int index = 0; index < segmentCount; index++) {
for (int code = startCount[index];
code <= endCount[index]; code++) {
if (range[index] == 0) {
glyphIndex = (delta[index] + code)
% Coder.USHORT_MAX;
} else {
location = rangeAdr[index] + range[index]
+ ((code - startCount[index]) << 1);
coder.move(location);
glyphIndex = coder.readUnsignedShort();
if (glyphIndex != 0) {
glyphIndex = (glyphIndex + delta[index])
% Coder.USHORT_MAX;
}
}
charToGlyph[code] = glyphIndex;
glyphToChar[glyphIndex] = code;
}
}
}
/**
* Decode the LOCA table.
*
* @param entry the bleEntry containing the encoded LOCA table data.
* @throws IOException if an error occurs while decoding the table data.
*/
private void decodeLOCA(final TableEntry entry) throws IOException {
final byte[] data = entry.getData();
final ByteArrayInputStream stream = new ByteArrayInputStream(data);
final BigDecoder coder = new BigDecoder(stream, data.length);
offsets = new int[glyphCount];
if (glyphOffset == ITLF_SHORT) {
offsets[0] = (coder.readUnsignedShort() * 2
<< BYTES_TO_BITS);
} else {
offsets[0] = (coder.readInt()
<< BYTES_TO_BITS);
}
for (int i = 1; i < glyphCount; i++) {
if (glyphOffset == ITLF_SHORT) {
offsets[i] = (coder.readUnsignedShort() * 2
<< BYTES_TO_BITS);
} else {
offsets[i] = (coder.readInt()
<< BYTES_TO_BITS);
}
if (offsets[i] == offsets[i - 1]) {
offsets[i - 1] = 0;
}
}
}
/**
* Decode the GLYF table.
*
* @param entry the bleEntry containing the encoded GLYF table data.
* @throws IOException if an error occurs while decoding the table data.
*/
private void decodeGlyphs(final TableEntry entry) throws IOException {
final byte[] data = entry.getData();
final ByteArrayInputStream stream = new ByteArrayInputStream(data);
final BigDecoder coder = new BigDecoder(stream, data.length);
int numberOfContours = 0;
for (int i = 0; i < glyphCount; i++) {
coder.skip(offsets[i] >> 3);
numberOfContours = coder.readShort();
if (numberOfContours >= 0) {
decodeSimpleGlyph(coder, i, numberOfContours);
}
coder.reset();
}
for (int i = 0; i < glyphCount; i++) {
if (offsets[i] != 0) {
coder.skip(offsets[i] >> 3);
if (coder.readShort() == -1) {
decodeCompositeGlyph(coder, i);
}
coder.reset();
}
}
}
/**
* Decode a simple glyph.
*
* @param coder the decoder containing the encoded glyph data.
* @param glyphIndex the position of the Glyph table to store the glyph.
* @param numberOfContours the number of segments in the glyph outline.
* @throws IOException if an error occurs reading the glyph data.
*/
private void decodeSimpleGlyph(final BigDecoder coder,
final int glyphIndex, final int numberOfContours)
throws IOException {
final int xMin = coder.readShort() / scale;
final int yMin = coder.readShort() / scale;
final int xMax = coder.readShort() / scale;
final int yMax = coder.readShort() / scale;
final int[] endPtsOfContours = new int[numberOfContours];
for (int i = 0; i < numberOfContours; i++) {
endPtsOfContours[i] = coder.readUnsignedShort();
}
final int instructionCount = coder.readUnsignedShort();
final int[] instructions = new int[instructionCount];
for (int i = 0; i < instructionCount; i++) {
instructions[i] = coder.readByte();
}
final int numberOfPoints = (numberOfContours == 0) ? 0
: endPtsOfContours[endPtsOfContours.length - 1] + 1;
final int[] flags = new int[numberOfPoints];
final int[] xCoordinates = new int[numberOfPoints];
final int[] yCoordinates = new int[numberOfPoints];
final boolean[] 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] = coder.readByte();
if ((flags[i] & REPEAT_FLAG) > 0) {
repeatCount = coder.readByte();
repeatFlag = flags[i];
}
}
onCurve[i] = (flags[i] & ON_CURVE) > 0;
}
int last = 0;
for (int i = 0; i < numberOfPoints; i++) {
if ((flags[i] & X_SHORT) > 0) {
if ((flags[i] & X_POSITIVE) > 0) {
xCoordinates[i] = last + coder.readByte();
last = xCoordinates[i];
} else {
xCoordinates[i] = last - coder.readByte();
last = xCoordinates[i];
}
} else {
if ((flags[i] & X_SAME) > 0) {
xCoordinates[i] = last;
} else {
xCoordinates[i] = last + coder.readShort();
last = xCoordinates[i];
}
}
}
last = 0;
for (int i = 0; i < numberOfPoints; i++) {
if ((flags[i] & Y_SHORT) > 0) {
if ((flags[i] & Y_POSITIVE) > 0) {
yCoordinates[i] = last + coder.readByte();
last = yCoordinates[i];
} else {
yCoordinates[i] = last - coder.readByte();
last = yCoordinates[i];
}
} else {
if ((flags[i] & Y_SAME) > 0) {
yCoordinates[i] = last;
} else {
yCoordinates[i] = last + coder.readShort();
last = yCoordinates[i];
}
}
}
/*
* Convert the coordinates into a shape
*/
final Canvas path = new Canvas();
boolean contourStart = true;
boolean offPoint = false;
int contour = 0;
int xCoord = 0;
int yCoord = 0;
int prevX = 0;
int prevY = 0;
int initX = 0;
int initY = 0;
for (int i = 0; i < numberOfPoints; i++) {
xCoord = xCoordinates[i] / scale;
yCoord = yCoordinates[i] / scale;
if (onCurve[i]) {
if (contourStart) {
path.moveForFont(xCoord, -yCoord);
contourStart = false;
initX = xCoord;
initY = yCoord;
} else if (offPoint) {
path.curve(prevX, -prevY, xCoord, -yCoord);
offPoint = false;
} else {
path.line(xCoord, -yCoord);
}
} else {
if (offPoint) {
path.curve(prevX, -prevY, (xCoord + prevX) / 2,
-(yCoord + prevY) / 2);
}
prevX = xCoord;
prevY = yCoord;
offPoint = true;
}
if (i == endPtsOfContours[contour]) {
if (offPoint) {
path.curve(xCoord, -yCoord, initX, -initY);
} else {
path.close();
}
contourStart = true;
offPoint = false;
prevX = 0;
prevY = 0;
contour++;
}
}
glyphTable[glyphIndex] = new TrueTypeGlyph(path.getShape(),
new Bounds(xMin, -yMax, xMax, -yMin), 0);
glyphTable[glyphIndex].setCoordinates(xCoordinates, yCoordinates);
glyphTable[glyphIndex].setOnCurve(onCurve);
glyphTable[glyphIndex].setEnds(endPtsOfContours);
}
/**
* Decode a glyph that contains a series of simple glyphs.
*
* @param coder the decoder containing the encoded glyph data.
* @param glyphIndex the position of the Glyph table to store the glyph.
* @throws IOException if an error occurs reading the glyph data.
*/
private void decodeCompositeGlyph(final BigDecoder coder,
final int glyphIndex) throws IOException {
final Shape shape = new Shape(new ArrayList<ShapeRecord>());
CoordTransform transform = null;
final int xMin = coder.readShort();
final int yMin = coder.readShort();
final int xMax = coder.readShort();
final int yMax = coder.readShort();
TrueTypeGlyph points = null;
int numberOfPoints = 0;
int[] endPtsOfContours = null;
int[] xCoordinates = null;
int[] yCoordinates = null;
boolean[] onCurve = null;
int flags = 0;
int sourceGlyph = 0;
int xOffset = 0;
int yOffset = 0;
// int sourceIndex = 0;
// int destIndex = 0;
do {
flags = coder.readUnsignedShort();
sourceGlyph = coder.readUnsignedShort();
if ((sourceGlyph >= glyphTable.length)
|| (glyphTable[sourceGlyph] == null)) {
glyphTable[glyphIndex] = new TrueTypeGlyph(null,
new Bounds(xMin, yMin, xMax, yMax), 0);
return;
}
points = glyphTable[sourceGlyph];
numberOfPoints = points.numberOfPoints();
endPtsOfContours = new int[points.numberOfContours()];
points.getEnd(endPtsOfContours);
xCoordinates = new int[numberOfPoints];
points.getXCoordinates(xCoordinates);
yCoordinates = new int[numberOfPoints];
points.getYCoordinates(yCoordinates);
onCurve = new boolean[numberOfPoints];
points.getCurve(onCurve);
if (((flags & ARGS_ARE_WORDS) == 0)
&& ((flags & ARGS_ARE_XY) == 0)) {
/* destIndex = */ coder.readByte();
/* sourceIndex = */ coder.readByte();
//xCoordinates[destIndex] =
//glyphTable[sourceGlyph].xCoordinates[sourceIndex];
//yCoordinates[destIndex] =
//glyphTable[sourceGlyph].yCoordinates[sourceIndex];
transform = CoordTransform.translate(0, 0);
} else if (((flags & ARGS_ARE_WORDS) == 0)
&& ((flags & ARGS_ARE_XY) > 0)) {
xOffset = (coder.readByte() << SIGN_EXTEND) >> SIGN_EXTEND;
yOffset = (coder.readByte() << SIGN_EXTEND) >> SIGN_EXTEND;
transform = CoordTransform.translate(xOffset, yOffset);
} else if (((flags & ARGS_ARE_WORDS) > 0)
&& ((flags & ARGS_ARE_XY) == 0)) {
/* destIndex = */ coder.readUnsignedShort();
/* sourceIndex = */ coder.readUnsignedShort();
//xCoordinates[destIndex] =
//glyphTable[sourceGlyph].xCoordinates[sourceIndex];
//yCoordinates[destIndex] =
//glyphTable[sourceGlyph].yCoordinates[sourceIndex];
transform = CoordTransform.translate(0, 0);
} else {
xOffset = coder.readShort();
yOffset = coder.readShort();
transform = CoordTransform.translate(xOffset, yOffset);
}
if ((flags & HAVE_SCALE) > 0) {
final float scaleXY = coder.readShort() / Coder.SCALE_14;
transform = new CoordTransform(scaleXY, scaleXY, 0, 0, xOffset,
yOffset);
} else if ((flags & HAVE_XYSCALE) > 0) {
final float scaleX = coder.readShort() / Coder.SCALE_14;
final float scaleY = coder.readShort() / Coder.SCALE_14;
transform = new CoordTransform(scaleX, scaleY, 0, 0, xOffset,
yOffset);
} else if ((flags & HAVE_2X2) > 0) {
final float scaleX = coder.readShort() / Coder.SCALE_14;
final float scale01 = coder.readShort() / Coder.SCALE_14;
final float scale10 = coder.readShort() / Coder.SCALE_14;
final float scaleY = coder.readShort() / Coder.SCALE_14;
transform = new CoordTransform(scaleX, scaleY, scale01,
scale10, xOffset, yOffset);
}
final float[][] matrix = transform.getMatrix();
float[][] result;
for (int i = 0; i < numberOfPoints; i++) {
result = CoordTransform.product(matrix, CoordTransform
.translate(xCoordinates[i], yCoordinates[i])
.getMatrix());
xCoordinates[i] = (int) result[0][2];
yCoordinates[i] = (int) result[1][2];
}
final Canvas path = new Canvas();
boolean contourStart = true;
boolean offPoint = false;
int contour = 0;
int xCoord = 0;
int yCoord = 0;
int prevX = 0;
int prevY = 0;
int initX = 0;
int initY = 0;
for (int i = 0; i < numberOfPoints; i++) {
xCoord = xCoordinates[i] / scale;
yCoord = yCoordinates[i] / scale;
if (onCurve[i]) {
if (contourStart) {
path.moveForFont(xCoord, -yCoord);
contourStart = false;
initX = xCoord;
initY = yCoord;
} else if (offPoint) {
path.curve(prevX, -prevY, xCoord, -yCoord);
offPoint = false;
} else {
path.line(xCoord, -yCoord);
}
} else {
if (offPoint) {
path.curve(prevX, -prevY, (xCoord + prevX) / 2,
-(yCoord + prevY) / 2);
}
prevX = xCoord;
prevY = yCoord;
offPoint = true;
}
if (i == endPtsOfContours[contour]) {
if (offPoint) {
path.curve(xCoord, -yCoord, initX, -initY);
} else {
path.close();
}
contourStart = true;
offPoint = false;
prevX = 0;
prevY = 0;
contour++;
}
}
shape.getObjects().addAll(path.getShape().getObjects());
} while ((flags & HAS_MORE) > 0);
glyphTable[glyphIndex] = new TrueTypeGlyph(shape,
new Bounds(xMin, yMin, xMax, yMax), 0);
glyphTable[glyphIndex].setCoordinates(xCoordinates, yCoordinates);
glyphTable[glyphIndex].setOnCurve(onCurve);
glyphTable[glyphIndex].setEnds(endPtsOfContours);
}
}