/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.fontbox.ttf;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
abstract class AbstractTTFParser {
protected boolean isEmbedded = false;
public AbstractTTFParser(boolean isEmbedded) {
this.isEmbedded = isEmbedded;
}
/**
* Parse a file and get a true type font.
* @param ttfFile The TTF file.
* @return A true type font.
* @throws IOException If there is an error parsing the true type font.
*/
public TrueTypeFont parseTTF( String ttfFile ) throws IOException
{
RAFDataStream raf = new RAFDataStream( ttfFile, "r" );
return parseTTF( raf );
}
/**
* Parse a file and get a true type font.
* @param ttfFile The TTF file.
* @return A true type font.
* @throws IOException If there is an error parsing the true type font.
*/
public TrueTypeFont parseTTF( File ttfFile ) throws IOException
{
RAFDataStream raf = new RAFDataStream( ttfFile, "r" );
return parseTTF( raf );
}
/**
* Parse a file and get a true type font.
* @param ttfData The TTF data to parse.
* @return A true type font.
* @throws IOException If there is an error parsing the true type font.
*/
public TrueTypeFont parseTTF( InputStream ttfData ) throws IOException
{
return parseTTF( new MemoryTTFDataStream( ttfData ));
}
/**
* Parse a file and get a true type font.
* @param raf The TTF file.
* @return A true type font.
* @throws IOException If there is an error parsing the true type font.
*/
public TrueTypeFont parseTTF( TTFDataStream raf ) throws IOException
{
TrueTypeFont font = new TrueTypeFont( raf );
font.setVersion( raf.read32Fixed() );
int numberOfTables = raf.readUnsignedShort();
int searchRange = raf.readUnsignedShort();
int entrySelector = raf.readUnsignedShort();
int rangeShift = raf.readUnsignedShort();
for( int i=0; i<numberOfTables; i++ )
{
TTFTable table = readTableDirectory( raf );
font.addTable( table );
}
//need to initialize a couple tables in a certain order
parseTables(font, raf);
return font;
}
/**
* Parse all tables and check if all needed tables are present.
* @param font the TrueTypeFont instance holding the parsed data.
* @param raf the data stream of the to be parsed ttf font
* @throws IOException If there is an error parsing the true type font.
*/
protected void parseTables(TrueTypeFont font, TTFDataStream raf)
throws IOException {
List<TTFTable> initialized = new ArrayList<TTFTable>();
HeaderTable head = font.getHeader();
if (head == null)
{
throw new IOException("head is mandatory");
}
raf.seek( head.getOffset() );
head.initData( font, raf );
initialized.add( head );
HorizontalHeaderTable hh = font.getHorizontalHeader();
if (hh == null)
{
throw new IOException("hhead is mandatory");
}
raf.seek( hh.getOffset() );
hh.initData( font, raf );
initialized.add( hh );
MaximumProfileTable maxp = font.getMaximumProfile();
if (maxp != null)
{
raf.seek( maxp.getOffset() );
maxp.initData( font, raf );
initialized.add( maxp );
}
else
{
throw new IOException("maxp is mandatory");
}
PostScriptTable post = font.getPostScript();
if (post != null) {
raf.seek( post.getOffset() );
post.initData( font, raf );
initialized.add( post );
}
else if ( !isEmbedded )
{
// in an embedded font this table is optional
throw new IOException("post is mandatory");
}
IndexToLocationTable loc = font.getIndexToLocation();
if (loc == null)
{
throw new IOException("loca is mandatory");
}
raf.seek( loc.getOffset() );
loc.initData( font, raf );
initialized.add( loc );
boolean cvt = false, prep = false, fpgm = false;
Iterator<TTFTable> iter = font.getTables().iterator();
while( iter.hasNext() )
{
TTFTable table = iter.next();
if( !initialized.contains( table ) )
{
raf.seek( table.getOffset() );
table.initData( font, raf );
}
if (table.getTag().startsWith("cvt"))
{
cvt = true;
}
else if ("prep".equals(table.getTag()))
{
prep = true;
}
else if ("fpgm".equals(table.getTag()))
{
fpgm = true;
}
}
// check others mandatory tables
if ( font.getGlyph() == null )
{
throw new IOException("glyf is mandatory");
}
if ( font.getNaming() == null && !isEmbedded )
{
throw new IOException("name is mandatory");
}
if ( font.getHorizontalMetrics() == null )
{
throw new IOException("hmtx is mandatory");
}
if (isEmbedded) {
// in a embedded truetype font prep, cvt_ and fpgm tables
// are mandatory
if (!fpgm)
{
throw new IOException("fpgm is mandatory");
}
if (!prep)
{
throw new IOException("prep is mandatory");
}
if (!cvt)
{
throw new IOException("cvt_ is mandatory");
}
}
}
private TTFTable readTableDirectory( TTFDataStream raf ) throws IOException
{
TTFTable retval = null;
String tag = raf.readString( 4 );
if( tag.equals( CMAPTable.TAG ) )
{
retval = new CMAPTable();
}
else if( tag.equals( GlyphTable.TAG ) )
{
retval = new GlyphTable();
}
else if( tag.equals( HeaderTable.TAG ) )
{
retval = new HeaderTable();
}
else if( tag.equals( HorizontalHeaderTable.TAG ) )
{
retval = new HorizontalHeaderTable();
}
else if( tag.equals( HorizontalMetricsTable.TAG ) )
{
retval = new HorizontalMetricsTable();
}
else if( tag.equals( IndexToLocationTable.TAG ) )
{
retval = new IndexToLocationTable();
}
else if( tag.equals( MaximumProfileTable.TAG ) )
{
retval = new MaximumProfileTable();
}
else if( tag.equals( NamingTable.TAG ) )
{
retval = new NamingTable();
}
else if( tag.equals( OS2WindowsMetricsTable.TAG ) )
{
retval = new OS2WindowsMetricsTable();
}
else if( tag.equals( PostScriptTable.TAG ) )
{
retval = new PostScriptTable();
}
else if( tag.equals( DigitalSignatureTable.TAG ) )
{
retval = new DigitalSignatureTable();
}
else
{
//unknown table type but read it anyway.
retval = new TTFTable();
}
retval.setTag( tag );
retval.setCheckSum( raf.readUnsignedInt() );
retval.setOffset( raf.readUnsignedInt() );
retval.setLength( raf.readUnsignedInt() );
return retval;
}
}