/*
* 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.afm;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import org.apache.fontbox.util.BoundingBox;
import org.apache.fontbox.util.Charsets;
/**
* This class is used to parse AFM(Adobe Font Metrics) documents.
*
* @see <A href="http://partners.adobe.com/asn/developer/type/">AFM Documentation</A>
*
* @author Ben Litchfield
*
*/
public class AFMParser
{
/**
* This is a comment in a AFM file.
*/
public static final String COMMENT = "Comment";
/**
* This is the constant used in the AFM file to start a font metrics item.
*/
public static final String START_FONT_METRICS = "StartFontMetrics";
/**
* This is the constant used in the AFM file to end a font metrics item.
*/
public static final String END_FONT_METRICS = "EndFontMetrics";
/**
* This is the font name.
*/
public static final String FONT_NAME = "FontName";
/**
* This is the full name.
*/
public static final String FULL_NAME = "FullName";
/**
* This is the Family name.
*/
public static final String FAMILY_NAME = "FamilyName";
/**
* This is the weight.
*/
public static final String WEIGHT = "Weight";
/**
* This is the font bounding box.
*/
public static final String FONT_BBOX = "FontBBox";
/**
* This is the version of the font.
*/
public static final String VERSION = "Version";
/**
* This is the notice.
*/
public static final String NOTICE = "Notice";
/**
* This is the encoding scheme.
*/
public static final String ENCODING_SCHEME = "EncodingScheme";
/**
* This is the mapping scheme.
*/
public static final String MAPPING_SCHEME = "MappingScheme";
/**
* This is the escape character.
*/
public static final String ESC_CHAR = "EscChar";
/**
* This is the character set.
*/
public static final String CHARACTER_SET = "CharacterSet";
/**
* This is the characters attribute.
*/
public static final String CHARACTERS = "Characters";
/**
* This will determine if this is a base font.
*/
public static final String IS_BASE_FONT = "IsBaseFont";
/**
* This is the V Vector attribute.
*/
public static final String V_VECTOR = "VVector";
/**
* This will tell if the V is fixed.
*/
public static final String IS_FIXED_V = "IsFixedV";
/**
* This is the cap height attribute.
*/
public static final String CAP_HEIGHT = "CapHeight";
/**
* This is the X height.
*/
public static final String X_HEIGHT = "XHeight";
/**
* This is ascender attribute.
*/
public static final String ASCENDER = "Ascender";
/**
* This is the descender attribute.
*/
public static final String DESCENDER = "Descender";
/**
* The underline position.
*/
public static final String UNDERLINE_POSITION = "UnderlinePosition";
/**
* This is the Underline thickness.
*/
public static final String UNDERLINE_THICKNESS = "UnderlineThickness";
/**
* This is the italic angle.
*/
public static final String ITALIC_ANGLE = "ItalicAngle";
/**
* This is the char width.
*/
public static final String CHAR_WIDTH = "CharWidth";
/**
* This will determine if this is fixed pitch.
*/
public static final String IS_FIXED_PITCH = "IsFixedPitch";
/**
* This is the start of character metrics.
*/
public static final String START_CHAR_METRICS = "StartCharMetrics";
/**
* This is the end of character metrics.
*/
public static final String END_CHAR_METRICS = "EndCharMetrics";
/**
* The character metrics c value.
*/
public static final String CHARMETRICS_C = "C";
/**
* The character metrics c value.
*/
public static final String CHARMETRICS_CH = "CH";
/**
* The character metrics value.
*/
public static final String CHARMETRICS_WX = "WX";
/**
* The character metrics value.
*/
public static final String CHARMETRICS_W0X = "W0X";
/**
* The character metrics value.
*/
public static final String CHARMETRICS_W1X = "W1X";
/**
* The character metrics value.
*/
public static final String CHARMETRICS_WY = "WY";
/**
* The character metrics value.
*/
public static final String CHARMETRICS_W0Y = "W0Y";
/**
* The character metrics value.
*/
public static final String CHARMETRICS_W1Y = "W1Y";
/**
* The character metrics value.
*/
public static final String CHARMETRICS_W = "W";
/**
* The character metrics value.
*/
public static final String CHARMETRICS_W0 = "W0";
/**
* The character metrics value.
*/
public static final String CHARMETRICS_W1 = "W1";
/**
* The character metrics value.
*/
public static final String CHARMETRICS_VV = "VV";
/**
* The character metrics value.
*/
public static final String CHARMETRICS_N = "N";
/**
* The character metrics value.
*/
public static final String CHARMETRICS_B = "B";
/**
* The character metrics value.
*/
public static final String CHARMETRICS_L = "L";
/**
* The character metrics value.
*/
public static final String STD_HW = "StdHW";
/**
* The character metrics value.
*/
public static final String STD_VW = "StdVW";
/**
* This is the start of track kern data.
*/
public static final String START_TRACK_KERN = "StartTrackKern";
/**
* This is the end of track kern data.
*/
public static final String END_TRACK_KERN = "EndTrackKern";
/**
* This is the start of kern data.
*/
public static final String START_KERN_DATA = "StartKernData";
/**
* This is the end of kern data.
*/
public static final String END_KERN_DATA = "EndKernData";
/**
* This is the start of kern pairs data.
*/
public static final String START_KERN_PAIRS = "StartKernPairs";
/**
* This is the end of kern pairs data.
*/
public static final String END_KERN_PAIRS = "EndKernPairs";
/**
* This is the start of kern pairs data.
*/
public static final String START_KERN_PAIRS0 = "StartKernPairs0";
/**
* This is the start of kern pairs data.
*/
public static final String START_KERN_PAIRS1 = "StartKernPairs1";
/**
* This is the start compisites data section.
*/
public static final String START_COMPOSITES = "StartComposites";
/**
* This is the end compisites data section.
*/
public static final String END_COMPOSITES = "EndComposites";
/**
* This is a composite character.
*/
public static final String CC = "CC";
/**
* This is a composite charater part.
*/
public static final String PCC = "PCC";
/**
* This is a kern pair.
*/
public static final String KERN_PAIR_KP = "KP";
/**
* This is a kern pair.
*/
public static final String KERN_PAIR_KPH = "KPH";
/**
* This is a kern pair.
*/
public static final String KERN_PAIR_KPX = "KPX";
/**
* This is a kern pair.
*/
public static final String KERN_PAIR_KPY = "KPY";
private static final int BITS_IN_HEX = 16;
private final InputStream input;
/**
* Constructor.
*
* @param in The input stream to read the AFM document from.
*/
public AFMParser( InputStream in )
{
input = in;
}
/**
* This will parse the AFM document. The input stream is closed
* when the parsing is finished.
*
* @return the parsed FontMetric
*
* @throws IOException If there is an IO error reading the document.
*/
public FontMetrics parse() throws IOException
{
return parseFontMetric(false);
}
/**
* This will parse the AFM document. The input stream is closed
* when the parsing is finished.
*
* @param reducedDataset parse a reduced subset of data if set to true
* @return the parsed FontMetric
*
* @throws IOException If there is an IO error reading the document.
*/
public FontMetrics parse(boolean reducedDataset) throws IOException
{
return parseFontMetric(reducedDataset);
}
/**
* This will parse a font metrics item.
*
* @return The parse font metrics item.
*
* @throws IOException If there is an error reading the AFM file.
*/
private FontMetrics parseFontMetric(boolean reducedDataset) throws IOException
{
FontMetrics fontMetrics = new FontMetrics();
String startFontMetrics = readString();
if( !START_FONT_METRICS.equals( startFontMetrics ) )
{
throw new IOException( "Error: The AFM file should start with " + START_FONT_METRICS +
" and not '" + startFontMetrics + "'" );
}
fontMetrics.setAFMVersion( readFloat() );
String nextCommand;
boolean charMetricsRead = false;
while (!END_FONT_METRICS.equals(nextCommand = readString()))
{
switch (nextCommand)
{
case FONT_NAME:
fontMetrics.setFontName( readLine() );
break;
case FULL_NAME:
fontMetrics.setFullName( readLine() );
break;
case FAMILY_NAME:
fontMetrics.setFamilyName( readLine() );
break;
case WEIGHT:
fontMetrics.setWeight( readLine() );
break;
case FONT_BBOX:
BoundingBox bBox = new BoundingBox();
bBox.setLowerLeftX( readFloat() );
bBox.setLowerLeftY( readFloat() );
bBox.setUpperRightX( readFloat() );
bBox.setUpperRightY( readFloat() );
fontMetrics.setFontBBox( bBox );
break;
case VERSION:
fontMetrics.setFontVersion( readLine() );
break;
case NOTICE:
fontMetrics.setNotice( readLine() );
break;
case ENCODING_SCHEME:
fontMetrics.setEncodingScheme( readLine() );
break;
case MAPPING_SCHEME:
fontMetrics.setMappingScheme( readInt() );
break;
case ESC_CHAR:
fontMetrics.setEscChar( readInt() );
break;
case CHARACTER_SET:
fontMetrics.setCharacterSet( readLine() );
break;
case CHARACTERS:
fontMetrics.setCharacters( readInt() );
break;
case IS_BASE_FONT:
fontMetrics.setIsBaseFont( readBoolean() );
break;
case V_VECTOR:
float[] vector = new float[2];
vector[0] = readFloat();
vector[1] = readFloat();
fontMetrics.setVVector( vector );
break;
case IS_FIXED_V:
fontMetrics.setIsFixedV( readBoolean() );
break;
case CAP_HEIGHT:
fontMetrics.setCapHeight( readFloat() );
break;
case X_HEIGHT:
fontMetrics.setXHeight( readFloat() );
break;
case ASCENDER:
fontMetrics.setAscender( readFloat() );
break;
case DESCENDER:
fontMetrics.setDescender( readFloat() );
break;
case STD_HW:
fontMetrics.setStandardHorizontalWidth( readFloat() );
break;
case STD_VW:
fontMetrics.setStandardVerticalWidth( readFloat() );
break;
case COMMENT:
fontMetrics.addComment( readLine() );
break;
case UNDERLINE_POSITION:
fontMetrics.setUnderlinePosition( readFloat() );
break;
case UNDERLINE_THICKNESS:
fontMetrics.setUnderlineThickness( readFloat() );
break;
case ITALIC_ANGLE:
fontMetrics.setItalicAngle( readFloat() );
break;
case CHAR_WIDTH:
float[] widths = new float[2];
widths[0] = readFloat();
widths[1] = readFloat();
fontMetrics.setCharWidth( widths );
break;
case IS_FIXED_PITCH:
fontMetrics.setFixedPitch( readBoolean() );
break;
case START_CHAR_METRICS:
int countMetrics = readInt();
List<CharMetric> charMetrics = new ArrayList<>(countMetrics);
for (int i = 0; i < countMetrics; i++)
{
CharMetric charMetric = parseCharMetric();
charMetrics.add( charMetric );
}
String endCharMetrics = readString();
if (!endCharMetrics.equals(END_CHAR_METRICS))
{
throw new IOException( "Error: Expected '" + END_CHAR_METRICS + "' actual '" +
endCharMetrics + "'");
}
charMetricsRead = true;
fontMetrics.setCharMetrics(charMetrics);
break;
case START_COMPOSITES:
if( !reducedDataset)
{
int countComposites = readInt();
for (int i = 0; i < countComposites; i++)
{
Composite part = parseComposite();
fontMetrics.addComposite( part );
}
String endComposites = readString();
if (!endComposites.equals(END_COMPOSITES))
{
throw new IOException( "Error: Expected '" + END_COMPOSITES + "' actual '" +
endComposites + "'");
}
}
break;
case START_KERN_DATA:
if( !reducedDataset)
{
parseKernData( fontMetrics );
}
break;
default:
if (reducedDataset && charMetricsRead)
{
break;
}
throw new IOException( "Unknown AFM key '" + nextCommand + "'" );
}
}
return fontMetrics;
}
/**
* This will parse the kern data.
*
* @param fontMetrics The metrics class to put the parsed data into.
*
* @throws IOException If there is an error parsing the data.
*/
private void parseKernData( FontMetrics fontMetrics ) throws IOException
{
String nextCommand;
while( !(nextCommand = readString()).equals( END_KERN_DATA ) )
{
switch(nextCommand)
{
case START_TRACK_KERN:
int countTrackKern = readInt();
for (int i = 0; i < countTrackKern; i++)
{
TrackKern kern = new TrackKern();
kern.setDegree( readInt() );
kern.setMinPointSize( readFloat() );
kern.setMinKern( readFloat() );
kern.setMaxPointSize( readFloat() );
kern.setMaxKern( readFloat() );
fontMetrics.addTrackKern( kern );
}
String endTrackKern = readString();
if (!endTrackKern.equals(END_TRACK_KERN))
{
throw new IOException( "Error: Expected '" + END_TRACK_KERN + "' actual '" +
endTrackKern + "'");
}
break;
case START_KERN_PAIRS:
int countKernPairs = readInt();
for (int i = 0; i < countKernPairs; i++)
{
KernPair pair = parseKernPair();
fontMetrics.addKernPair( pair );
}
String endKernPairs = readString();
if (!endKernPairs.equals(END_KERN_PAIRS))
{
throw new IOException( "Error: Expected '" + END_KERN_PAIRS + "' actual '" +
endKernPairs + "'");
}
break;
case START_KERN_PAIRS0:
int countKernPairs0 = readInt();
for (int i = 0; i < countKernPairs0; i++)
{
KernPair pair = parseKernPair();
fontMetrics.addKernPair0( pair );
}
String endKernPairs0 = readString();
if (!endKernPairs0.equals(END_KERN_PAIRS))
{
throw new IOException( "Error: Expected '" + END_KERN_PAIRS + "' actual '" +
endKernPairs0 + "'");
}
break;
case START_KERN_PAIRS1:
int countKernPairs1 = readInt();
for (int i = 0; i < countKernPairs1; i++)
{
KernPair pair = parseKernPair();
fontMetrics.addKernPair1( pair );
}
String endKernPairs1 = readString();
if (!endKernPairs1.equals(END_KERN_PAIRS))
{
throw new IOException( "Error: Expected '" + END_KERN_PAIRS + "' actual '" +
endKernPairs1 + "'");
}
break;
default:
throw new IOException( "Unknown kerning data type '" + nextCommand + "'" );
}
}
}
/**
* This will parse a kern pair from the data stream.
*
* @return The kern pair that was parsed from the stream.
*
* @throws IOException If there is an error reading from the stream.
*/
private KernPair parseKernPair() throws IOException
{
KernPair kernPair = new KernPair();
String cmd = readString();
switch(cmd)
{
case KERN_PAIR_KP:
kernPair.setFirstKernCharacter(readString());
kernPair.setSecondKernCharacter(readString());
kernPair.setX(readFloat());
kernPair.setY(readFloat());
break;
case KERN_PAIR_KPH:
kernPair.setFirstKernCharacter(hexToString(readString()));
kernPair.setSecondKernCharacter(hexToString(readString()));
kernPair.setX(readFloat());
kernPair.setY(readFloat());
break;
case KERN_PAIR_KPX:
kernPair.setFirstKernCharacter(readString());
kernPair.setSecondKernCharacter(readString());
kernPair.setX(readFloat());
kernPair.setY( 0 );
break;
case KERN_PAIR_KPY:
kernPair.setFirstKernCharacter(readString());
kernPair.setSecondKernCharacter(readString());
kernPair.setX( 0 );
kernPair.setY(readFloat());
break;
default:
throw new IOException( "Error expected kern pair command actual='" + cmd + "'" );
}
return kernPair;
}
/**
* This will convert and angle bracket hex string to a string.
*
* @param hexString An angle bracket string.
*
* @return The bytes of the hex string.
*
* @throws IOException If the string is in an invalid format.
*/
private String hexToString( String hexString ) throws IOException
{
if( hexString.length() < 2 )
{
throw new IOException( "Error: Expected hex string of length >= 2 not='" + hexString );
}
if( hexString.charAt( 0 ) != '<' ||
hexString.charAt( hexString.length() -1 ) != '>' )
{
throw new IOException( "String should be enclosed by angle brackets '" + hexString+ "'" );
}
hexString = hexString.substring( 1, hexString.length() -1 );
byte[] data = new byte[ (hexString.length() / 2) ];
for( int i=0; i<hexString.length(); i+=2 )
{
String hex = "" + hexString.charAt( i ) + hexString.charAt( i+1 );
try
{
data[ i / 2 ] = (byte)Integer.parseInt( hex, BITS_IN_HEX );
}
catch( NumberFormatException e )
{
throw new IOException( "Error parsing AFM file:" + e );
}
}
return new String( data, Charsets.ISO_8859_1 );
}
/**
* This will parse a composite part from the stream.
*
* @return The composite.
*
* @throws IOException If there is an error parsing the composite.
*/
private Composite parseComposite() throws IOException
{
Composite composite = new Composite();
String partData = readLine();
StringTokenizer tokenizer = new StringTokenizer( partData, " ;" );
String cc = tokenizer.nextToken();
if( !cc.equals( CC ) )
{
throw new IOException( "Expected '" + CC + "' actual='" + cc + "'" );
}
String name = tokenizer.nextToken();
composite.setName( name );
int partCount;
try
{
partCount = Integer.parseInt( tokenizer.nextToken() );
}
catch( NumberFormatException e )
{
throw new IOException( "Error parsing AFM document:" + e );
}
for( int i=0; i<partCount; i++ )
{
CompositePart part = new CompositePart();
String pcc = tokenizer.nextToken();
if( !pcc.equals( PCC ) )
{
throw new IOException( "Expected '" + PCC + "' actual='" + pcc + "'" );
}
String partName = tokenizer.nextToken();
try
{
int x = Integer.parseInt( tokenizer.nextToken() );
int y = Integer.parseInt( tokenizer.nextToken() );
part.setName( partName );
part.setXDisplacement( x );
part.setYDisplacement( y );
composite.addPart( part );
}
catch( NumberFormatException e )
{
throw new IOException( "Error parsing AFM document:" + e );
}
}
return composite;
}
/**
* This will parse a single CharMetric object from the stream.
*
* @return The next char metric in the stream.
*
* @throws IOException If there is an error reading from the stream.
*/
private CharMetric parseCharMetric() throws IOException
{
CharMetric charMetric = new CharMetric();
String metrics = readLine();
StringTokenizer metricsTokenizer = new StringTokenizer( metrics );
try
{
while( metricsTokenizer.hasMoreTokens() )
{
String nextCommand = metricsTokenizer.nextToken();
switch(nextCommand)
{
case CHARMETRICS_C:
String charCodeC = metricsTokenizer.nextToken();
charMetric.setCharacterCode(Integer.parseInt(charCodeC));
verifySemicolon( metricsTokenizer );
break;
case CHARMETRICS_CH:
//Is the hex string <FF> or FF, the spec is a little
//unclear, wait and see if it breaks anything.
String charCodeCH = metricsTokenizer.nextToken();
charMetric.setCharacterCode(Integer.parseInt(charCodeCH, BITS_IN_HEX));
verifySemicolon( metricsTokenizer );
break;
case CHARMETRICS_WX:
charMetric.setWx(Float.parseFloat(metricsTokenizer.nextToken()));
verifySemicolon( metricsTokenizer );
break;
case CHARMETRICS_W0X:
charMetric.setW0x(Float.parseFloat(metricsTokenizer.nextToken()));
verifySemicolon( metricsTokenizer );
break;
case CHARMETRICS_W1X:
charMetric.setW1x(Float.parseFloat(metricsTokenizer.nextToken()));
verifySemicolon( metricsTokenizer );
break;
case CHARMETRICS_WY:
charMetric.setWy(Float.parseFloat(metricsTokenizer.nextToken()));
verifySemicolon( metricsTokenizer );
break;
case CHARMETRICS_W0Y:
charMetric.setW0y(Float.parseFloat(metricsTokenizer.nextToken()));
verifySemicolon( metricsTokenizer );
break;
case CHARMETRICS_W1Y:
charMetric.setW1y(Float.parseFloat(metricsTokenizer.nextToken()));
verifySemicolon( metricsTokenizer );
break;
case CHARMETRICS_W:
float[] w = new float[2];
w[0] = Float.parseFloat(metricsTokenizer.nextToken());
w[1] = Float.parseFloat(metricsTokenizer.nextToken());
charMetric.setW( w );
verifySemicolon( metricsTokenizer );
break;
case CHARMETRICS_W0:
float[] w0 = new float[2];
w0[0] = Float.parseFloat(metricsTokenizer.nextToken());
w0[1] = Float.parseFloat(metricsTokenizer.nextToken());
charMetric.setW0( w0 );
verifySemicolon( metricsTokenizer );
break;
case CHARMETRICS_W1:
float[] w1 = new float[2];
w1[0] = Float.parseFloat(metricsTokenizer.nextToken());
w1[1] = Float.parseFloat(metricsTokenizer.nextToken());
charMetric.setW1( w1 );
verifySemicolon( metricsTokenizer );
break;
case CHARMETRICS_VV:
float[] vv = new float[2];
vv[0] = Float.parseFloat(metricsTokenizer.nextToken());
vv[1] = Float.parseFloat(metricsTokenizer.nextToken());
charMetric.setVv( vv );
verifySemicolon( metricsTokenizer );
break;
case CHARMETRICS_N:
charMetric.setName(metricsTokenizer.nextToken());
verifySemicolon( metricsTokenizer );
break;
case CHARMETRICS_B:
BoundingBox box = new BoundingBox();
box.setLowerLeftX(Float.parseFloat(metricsTokenizer.nextToken()));
box.setLowerLeftY(Float.parseFloat(metricsTokenizer.nextToken()));
box.setUpperRightX(Float.parseFloat(metricsTokenizer.nextToken()));
box.setUpperRightY(Float.parseFloat(metricsTokenizer.nextToken()));
charMetric.setBoundingBox( box );
verifySemicolon( metricsTokenizer );
break;
case CHARMETRICS_L:
Ligature lig = new Ligature();
lig.setSuccessor(metricsTokenizer.nextToken());
lig.setLigature(metricsTokenizer.nextToken());
charMetric.addLigature( lig );
verifySemicolon( metricsTokenizer );
break;
default:
throw new IOException( "Unknown CharMetrics command '" + nextCommand + "'" );
}
}
}
catch( NumberFormatException e )
{
throw new IOException( "Error: Corrupt AFM document:" + e );
}
return charMetric;
}
/**
* This is used to verify that a semicolon is the next token in the stream.
*
* @param tokenizer The tokenizer to read from.
*
* @throws IOException If the semicolon is missing.
*/
private void verifySemicolon( StringTokenizer tokenizer ) throws IOException
{
if( tokenizer.hasMoreTokens() )
{
String semicolon = tokenizer.nextToken();
if( !semicolon.equals( ";" ) )
{
throw new IOException( "Error: Expected semicolon in stream actual='" +
semicolon + "'" );
}
}
else
{
throw new IOException( "CharMetrics is missing a semicolon after a command" );
}
}
/**
* This will read a boolean from the stream.
*
* @return The boolean in the stream.
*/
private boolean readBoolean() throws IOException
{
String theBoolean = readString();
return Boolean.valueOf( theBoolean );
}
/**
* This will read an integer from the stream.
*
* @return The integer in the stream.
*/
private int readInt() throws IOException
{
String theInt = readString();
try
{
return Integer.parseInt( theInt );
}
catch( NumberFormatException e )
{
throw new IOException( "Error parsing AFM document:" + e );
}
}
/**
* This will read a float from the stream.
*
* @return The float in the stream.
*/
private float readFloat() throws IOException
{
String theFloat = readString();
return Float.parseFloat( theFloat );
}
/**
* This will read until the end of a line.
*
* @return The string that is read.
*/
private String readLine() throws IOException
{
//First skip the whitespace
StringBuilder buf = new StringBuilder(60);
int nextByte = input.read();
while( isWhitespace( nextByte ) )
{
nextByte = input.read();
//do nothing just skip the whitespace.
}
buf.append( (char)nextByte );
//now read the data
while( !isEOL(nextByte = input.read()) )
{
buf.append( (char)nextByte );
}
return buf.toString();
}
/**
* This will read a string from the input stream and stop at any whitespace.
*
* @return The string read from the stream.
*
* @throws IOException If an IO error occurs when reading from the stream.
*/
private String readString() throws IOException
{
//First skip the whitespace
StringBuilder buf = new StringBuilder(24);
int nextByte = input.read();
while( isWhitespace( nextByte ) )
{
nextByte = input.read();
//do nothing just skip the whitespace.
}
buf.append( (char)nextByte );
//now read the data
while( !isWhitespace(nextByte = input.read()) )
{
buf.append( (char)nextByte );
}
return buf.toString();
}
/**
* This will determine if the byte is a whitespace character or not.
*
* @param character The character to test for whitespace.
*
* @return true If the character is whitespace as defined by the AFM spec.
*/
private boolean isEOL( int character )
{
return character == 0x0D ||
character == 0x0A;
}
/**
* This will determine if the byte is a whitespace character or not.
*
* @param character The character to test for whitespace.
*
* @return true If the character is whitespace as defined by the AFM spec.
*/
private boolean isWhitespace( int character )
{
return character == ' ' ||
character == '\t' ||
character == 0x0D ||
character == 0x0A;
}
}