/**
* ===========================================
* LibFonts : a free Java font reading library
* ===========================================
*
* Project Info: http://reporting.pentaho.org/libfonts/
*
* (C) Copyright 2006-2007, by Pentaho Corporation and Contributors.
*
* This library is free software; you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Foundation;
* either version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*
* [Java is a trademark or registered trademark of Sun Microsystems, Inc.
* in the United States and other countries.]
*
* ------------
* $Id: TrueTypeFont.java 3523 2007-10-16 11:03:09Z tmorgner $
* ------------
* (C) Copyright 2006-2007, by Pentaho Corporation.
*/
package org.jfree.fonts.truetype;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import org.jfree.fonts.ByteAccessUtilities;
import org.jfree.fonts.io.FileFontDataInputSource;
import org.jfree.fonts.io.FontDataInputSource;
import org.jfree.util.Log;
/**
* Creation-Date: 06.11.2005, 18:27:21
*
* @author Thomas Morgner
*/
public class TrueTypeFont
{
private static class TrueTypeFontHeader
{
public static final int ENTRY_LENGTH = 12;
private long version;
private int numTables;
private int searchRange;
private int entrySelector;
private int rangeShift;
protected TrueTypeFontHeader(final byte[] data)
{
this.version = ByteAccessUtilities.readULong(data, 0);
this.numTables = ByteAccessUtilities.readUShort(data, 4);
this.searchRange = ByteAccessUtilities.readUShort(data, 6);
this.entrySelector = ByteAccessUtilities.readUShort(data, 8);
this.rangeShift = ByteAccessUtilities.readUShort(data, 10);
}
public long getVersion()
{
return version;
}
public int getNumTables()
{
return numTables;
}
public int getSearchRange()
{
return searchRange;
}
public int getEntrySelector()
{
return entrySelector;
}
public int getRangeShift()
{
return rangeShift;
}
}
private static class TableDirectoryEntry
{
public static final int ENTRY_LENGTH = 16;
private long tag;
private long checkSum;
private int offset;
private int length;
private FontTable table;
protected TableDirectoryEntry(final byte[] data, final int offset)
{
this.tag = ByteAccessUtilities.readULong(data, offset);
this.checkSum = ByteAccessUtilities.readULong(data, offset + 4);
this.offset = (int) ByteAccessUtilities.readULong(data, offset + 8);
this.length = (int) ByteAccessUtilities.readULong(data, offset + 12);
}
public long getTag()
{
return tag;
}
public long getCheckSum()
{
return checkSum;
}
public int getOffset()
{
return offset;
}
public int getLength()
{
return length;
}
public FontTable getTable()
{
return table;
}
public void setTable(final FontTable table)
{
this.table = table;
}
/**
* Returns a string representation of the object. In general, the
* <code>toString</code> method returns a string that "textually represents"
* this object. The result should be a concise but informative
* representation that is easy for a person to read. It is recommended that
* all subclasses override this method.
* <p/>
* The <code>toString</code> method for class <code>Object</code> returns a
* string consisting of the name of the class of which the object is an
* instance, the at-sign character `<code>@</code>', and the unsigned
* hexadecimal representation of the hash code of the object. In other
* words, this method returns a string equal to the value of: <blockquote>
* <pre>
* getClass().getName() + '@' + Integer.toHexString(hashCode())
* </pre></blockquote>
*
* @return a string representation of the object.
*/
public String toString()
{
final char c1 = (char) ((tag >> 24) & 255);
final char c2 = (char) ((tag >> 16) & 255);
final char c3 = (char) ((tag >> 8) & 255);
final char c4 = (char) (tag & 255);
return "TableDirectoryEntry={" + c1 + c2 + c3 + c4 + ',' + table + '}';
}
}
private long offset;
private String filename;
private FontDataInputSource input;
private transient byte[] readBuffer;
private TableDirectoryEntry[] directory;
private TrueTypeFontHeader header;
private int collectionIndex;
public TrueTypeFont(final FontDataInputSource filename)
throws IOException
{
this(filename, 0, -1);
}
public TrueTypeFont(final FontDataInputSource filename, final long offset)
throws IOException
{
this(filename, offset, -1);
}
public TrueTypeFont(final FontDataInputSource filename,
final long offset,
final int collectionIndex)
throws IOException
{
if (offset < 0)
{
throw new IndexOutOfBoundsException();
}
this.collectionIndex = collectionIndex;
this.offset = offset;
this.input = filename;
this.filename = filename.getFileName();
this.header = new TrueTypeFontHeader
(readFully(offset, TrueTypeFontHeader.ENTRY_LENGTH));
this.directory = readTableDirectory();
}
public TrueTypeFont(final File filename)
throws IOException
{
this(filename, 0, -1);
}
public TrueTypeFont(final File filename, final long offset)
throws IOException
{
this(filename, offset, -1);
}
public TrueTypeFont(final File filename,
final long offset,
final int collectionIndex)
throws IOException
{
this(new FileFontDataInputSource(filename), offset, collectionIndex);
}
public int getCollectionIndex()
{
return collectionIndex;
}
private TableDirectoryEntry[] readTableDirectory() throws IOException
{
final int numTables = header.getNumTables();
final int directorySize =
numTables * TableDirectoryEntry.ENTRY_LENGTH;
final byte[] directoryData =
readFully(offset + TrueTypeFontHeader.ENTRY_LENGTH, directorySize);
final TableDirectoryEntry[] directory = new TableDirectoryEntry[numTables];
for (int i = 0; i < header.getNumTables(); i += 1)
{
final int dirOffset = TableDirectoryEntry.ENTRY_LENGTH * i;
directory[i] = new TableDirectoryEntry(directoryData, dirOffset);
}
return directory;
}
protected synchronized byte[] readFully(final long offset, final int length)
throws IOException
{
if (readBuffer == null)
{
readBuffer = new byte[Math.max(8192, length)];
}
else if (readBuffer.length < length)
{
readBuffer = new byte[length];
}
input.readFullyAt(offset, readBuffer, 0, length);
if ((readBuffer.length - length) > 0)
{
Arrays.fill(readBuffer, length, readBuffer.length, (byte) 0);
}
return readBuffer;
}
public long getOffset()
{
return offset;
}
/**
* The file that was used to load the font. This is deprecated, as only the
* transition version of JFreeReport is using this hack.
*
* @return
*/
public String getFilename()
{
return filename;
}
public FontTable getTable(final long key) throws IOException
{
for (int i = 0; i < directory.length; i++)
{
final TableDirectoryEntry entry = directory[i];
if (entry.getTag() == key)
{
final FontTable table = entry.getTable();
if (table != null)
{
return table;
}
final FontTable readTable = readTable(entry);
if (readTable == null)
{
return null;
}
entry.setTable(readTable);
return readTable;
}
}
// no such table in the font ..
return null;
}
protected synchronized FontTable readTable(final TableDirectoryEntry table)
throws IOException
{
if (table.getTag() == NameTable.TABLE_ID)
{
final byte[] buffer =
readFully(table.getOffset(), table.getLength());
return new NameTable(buffer);
}
if (table.getTag() == FontHeaderTable.TABLE_ID)
{
final byte[] buffer =
readFully(table.getOffset(), table.getLength());
return new FontHeaderTable(buffer);
}
if (table.getTag() == HorizontalHeaderTable.TABLE_ID)
{
final byte[] buffer =
readFully(table.getOffset(), table.getLength());
return new HorizontalHeaderTable(buffer);
}
if (table.getTag() == OS2Table.TABLE_ID)
{
final FontHeaderTable header =
(FontHeaderTable) getTable(FontHeaderTable.TABLE_ID);
if (header == null)
{
Log.warn(
"The font '" + filename + "' does not have a 'head' table. The font file is not valid.");
return null;
}
final byte[] buffer =
readFully(table.getOffset(), table.getLength());
return new OS2Table(buffer, header.getUnitsPerEm());
}
if (table.getTag() == PostscriptInformationTable.TABLE_ID)
{
final byte[] buffer =
readFully(table.getOffset(), table.getLength());
return new PostscriptInformationTable(buffer);
}
return null;
}
public void dispose()
{
input.dispose();
}
protected void finalize() throws Throwable
{
super.finalize();
dispose();
}
public FontDataInputSource getInputSource()
{
return input;
}
}