All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.fontbox.afm.AFMParser Maven / Gradle / Ivy

There is a newer version: 1.2.2.1-jre17
Show newest version
/*
 * 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 AFM Documentation
 *
 * @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 composites data section.
     */
    public static final String START_COMPOSITES = "StartComposites";
    /**
     * This is the end composites data section.
     */
    public static final String END_COMPOSITES = "EndComposites";
    /**
     * This is a composite character.
     */
    public static final String CC = "CC";
    /**
     * This is a composite character 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() ) ) )
        {
            if( FONT_NAME.equals( nextCommand ) )
            {
                fontMetrics.setFontName( readLine() );
            }
            else if( FULL_NAME.equals( nextCommand ) )
            {
                fontMetrics.setFullName( readLine() );
            }
            else if( FAMILY_NAME.equals( nextCommand ) )
            {
                fontMetrics.setFamilyName( readLine() );
            }
            else if( WEIGHT.equals( nextCommand ) )
            {
                fontMetrics.setWeight( readLine() );
            }
            else if( FONT_BBOX.equals( nextCommand ) )
            {
                BoundingBox bBox = new BoundingBox();
                bBox.setLowerLeftX( readFloat() );
                bBox.setLowerLeftY( readFloat() );
                bBox.setUpperRightX( readFloat() );
                bBox.setUpperRightY( readFloat() );
                fontMetrics.setFontBBox( bBox );
            }
            else if( VERSION.equals( nextCommand ) )
            {
                fontMetrics.setFontVersion( readLine() );
            }
            else if( NOTICE.equals( nextCommand ) )
            {
                fontMetrics.setNotice( readLine() );
            }
            else if( ENCODING_SCHEME.equals( nextCommand ) )
            {
                fontMetrics.setEncodingScheme( readLine() );
            }
            else if( MAPPING_SCHEME.equals( nextCommand ) )
            {
                fontMetrics.setMappingScheme( readInt() );
            }
            else if( ESC_CHAR.equals( nextCommand ) )
            {
                fontMetrics.setEscChar( readInt() );
            }
            else if( CHARACTER_SET.equals( nextCommand ) )
            {
                fontMetrics.setCharacterSet( readLine() );
            }
            else if( CHARACTERS.equals( nextCommand ) )
            {
                fontMetrics.setCharacters( readInt() );
            }
            else if( IS_BASE_FONT.equals( nextCommand ) )
            {
                fontMetrics.setIsBaseFont( readBoolean() );
            }
            else if( V_VECTOR.equals( nextCommand ) )
            {
                float[] vector = new float[2];
                vector[0] = readFloat();
                vector[1] = readFloat();
                fontMetrics.setVVector( vector );
            }
            else if( IS_FIXED_V.equals( nextCommand ) )
            {
                fontMetrics.setIsFixedV( readBoolean() );
            }
            else if( CAP_HEIGHT.equals( nextCommand ) )
            {
                fontMetrics.setCapHeight( readFloat() );
            }
            else if( X_HEIGHT.equals( nextCommand ) )
            {
                fontMetrics.setXHeight( readFloat() );
            }
            else if( ASCENDER.equals( nextCommand ) )
            {
                fontMetrics.setAscender( readFloat() );
            }
            else if( DESCENDER.equals( nextCommand ) )
            {
                fontMetrics.setDescender( readFloat() );
            }
            else if( STD_HW.equals( nextCommand ) )
            {
                fontMetrics.setStandardHorizontalWidth( readFloat() );
            }
            else if( STD_VW.equals( nextCommand ) )
            {
                fontMetrics.setStandardVerticalWidth( readFloat() );
            }
            else if( COMMENT.equals( nextCommand ) )
            {
                fontMetrics.addComment( readLine() );
            }
            else if( UNDERLINE_POSITION.equals( nextCommand ) )
            {
                fontMetrics.setUnderlinePosition( readFloat() );
            }
            else if( UNDERLINE_THICKNESS.equals( nextCommand ) )
            {
                fontMetrics.setUnderlineThickness( readFloat() );
            }
            else if( ITALIC_ANGLE.equals( nextCommand ) )
            {
                fontMetrics.setItalicAngle( readFloat() );
            }
            else if( CHAR_WIDTH.equals( nextCommand ) )
            {
                float[] widths = new float[2];
                widths[0] = readFloat();
                widths[1] = readFloat();
                fontMetrics.setCharWidth( widths );
            }
            else if( IS_FIXED_PITCH.equals( nextCommand ) )
            {
                fontMetrics.setFixedPitch( readBoolean() );
            }
            else if( START_CHAR_METRICS.equals( nextCommand ) )
            {
                int count = readInt();
                List charMetrics = new ArrayList(count);
                for( int i=0; i= 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 or FF, the spec is a little
                    //unclear, wait and see if it breaks anything.
                    String charCode = metricsTokenizer.nextToken();
                    charMetric.setCharacterCode( Integer.parseInt( charCode, BITS_IN_HEX ) );
                    verifySemicolon( metricsTokenizer );
                }
                else if( nextCommand.equals( CHARMETRICS_WX ) )
                {
                    charMetric.setWx(Float.parseFloat(metricsTokenizer.nextToken()));
                    verifySemicolon( metricsTokenizer );
                }
                else if( nextCommand.equals( CHARMETRICS_W0X ) )
                {
                    charMetric.setW0x(Float.parseFloat(metricsTokenizer.nextToken()));
                    verifySemicolon( metricsTokenizer );
                }
                else if( nextCommand.equals( CHARMETRICS_W1X ) )
                {
                    charMetric.setW1x(Float.parseFloat(metricsTokenizer.nextToken()));
                    verifySemicolon( metricsTokenizer );
                }
                else if( nextCommand.equals( CHARMETRICS_WY ) )
                {
                    charMetric.setWy(Float.parseFloat(metricsTokenizer.nextToken()));
                    verifySemicolon( metricsTokenizer );
                }
                else if( nextCommand.equals( CHARMETRICS_W0Y ) )
                {
                    charMetric.setW0y(Float.parseFloat(metricsTokenizer.nextToken()));
                    verifySemicolon( metricsTokenizer );
                }
                else if( nextCommand.equals( CHARMETRICS_W1Y ) )
                {
                    charMetric.setW1y(Float.parseFloat(metricsTokenizer.nextToken()));
                    verifySemicolon( metricsTokenizer );
                }
                else if( nextCommand.equals( CHARMETRICS_W ) )
                {
                    float[] w = new float[2];
                    w[0] = Float.parseFloat(metricsTokenizer.nextToken());
                    w[1] = Float.parseFloat(metricsTokenizer.nextToken());
                    charMetric.setW( w );
                    verifySemicolon( metricsTokenizer );
                }
                else if( nextCommand.equals( CHARMETRICS_W0 ) )
                {
                    float[] w0 = new float[2];
                    w0[0] = Float.parseFloat(metricsTokenizer.nextToken());
                    w0[1] = Float.parseFloat(metricsTokenizer.nextToken());
                    charMetric.setW0( w0 );
                    verifySemicolon( metricsTokenizer );
                }
                else if( nextCommand.equals( CHARMETRICS_W1 ) )
                {
                    float[] w1 = new float[2];
                    w1[0] = Float.parseFloat(metricsTokenizer.nextToken());
                    w1[1] = Float.parseFloat(metricsTokenizer.nextToken());
                    charMetric.setW1( w1 );
                    verifySemicolon( metricsTokenizer );
                }
                else if( nextCommand.equals( CHARMETRICS_VV ) )
                {
                    float[] vv = new float[2];
                    vv[0] = Float.parseFloat(metricsTokenizer.nextToken());
                    vv[1] = Float.parseFloat(metricsTokenizer.nextToken());
                    charMetric.setVv( vv );
                    verifySemicolon( metricsTokenizer );
                }
                else if( nextCommand.equals( CHARMETRICS_N ) )
                {
                    charMetric.setName(metricsTokenizer.nextToken());
                    verifySemicolon( metricsTokenizer );
                }
                else if( nextCommand.equals( 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 );
                }
                else if( nextCommand.equals( CHARMETRICS_L ) )
                {
                    Ligature lig = new Ligature();
                    lig.setSuccessor(metricsTokenizer.nextToken());
                    lig.setLigature(metricsTokenizer.nextToken());
                    charMetric.addLigature( lig );
                    verifySemicolon( metricsTokenizer );
                }
                else
                {
                    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 (!";".equals(semicolon))
            {
                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
        nextByte = input.read();
        while (nextByte != -1 && !isEOL(nextByte))
        {
            buf.append((char) nextByte);
            nextByte = input.read();
        }
        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
        nextByte = input.read();
        while (nextByte != -1 && !isWhitespace(nextByte))
        {
            buf.append((char) nextByte);
            nextByte = input.read();
        }
        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;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy