Home » pdfbox-1.1.0-src » org.apache.fontbox.afm » [javadoc | source]

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
   10    *
   11    * Unless required by applicable law or agreed to in writing, software
   12    * distributed under the License is distributed on an "AS IS" BASIS,
   13    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   14    * See the License for the specific language governing permissions and
   15    * limitations under the License.
   16    */
   17   package org.apache.fontbox.afm;
   18   
   19   import java.io.InputStream;
   20   import java.io.IOException;
   21   
   22   import java.util.StringTokenizer;
   23   
   24   import org.apache.fontbox.util.BoundingBox;
   25   
   26   /**
   27    * This class is used to parse AFM(Adobe Font Metrics) documents.
   28    *
   29    * @see <A href="http://partners.adobe.com/asn/developer/type/">AFM Documentation</A>
   30    *
   31    * @author Ben Litchfield (ben@benlitchfield.com)
   32    * @version $Revision: 1.1 $
   33    */
   34   public class AFMParser
   35   {
   36       /**
   37        * This is a comment in a AFM file.
   38        */
   39       public static final String COMMENT = "Comment";
   40       /**
   41        * This is the constant used in the AFM file to start a font metrics item.
   42        */
   43       public static final String START_FONT_METRICS = "StartFontMetrics";
   44       /**
   45        * This is the constant used in the AFM file to end a font metrics item.
   46        */
   47       public static final String END_FONT_METRICS = "EndFontMetrics";
   48       /**
   49        * This is the font name.
   50        */
   51       public static final String FONT_NAME = "FontName";
   52       /**
   53        * This is the full name.
   54        */
   55       public static final String FULL_NAME = "FullName";
   56       /**
   57        * This is the Family name.
   58        */
   59       public static final String FAMILY_NAME = "FamilyName";
   60       /**
   61        * This is the weight.
   62        */
   63       public static final String WEIGHT = "Weight";
   64       /**
   65        * This is the font bounding box.
   66        */
   67       public static final String FONT_BBOX = "FontBBox";
   68       /**
   69        * This is the version of the font.
   70        */
   71       public static final String VERSION = "Version";
   72       /**
   73        * This is the notice.
   74        */
   75       public static final String NOTICE = "Notice";
   76       /**
   77        * This is the encoding scheme.
   78        */
   79       public static final String ENCODING_SCHEME = "EncodingScheme";
   80       /**
   81        * This is the mapping scheme.
   82        */
   83       public static final String MAPPING_SCHEME = "MappingScheme";
   84       /**
   85        * This is the escape character.
   86        */
   87       public static final String ESC_CHAR = "EscChar";
   88       /**
   89        * This is the character set.
   90        */
   91       public static final String CHARACTER_SET = "CharacterSet";
   92       /**
   93        * This is the characters attribute.
   94        */
   95       public static final String CHARACTERS = "Characters";
   96       /**
   97        * This will determine if this is a base font.
   98        */
   99       public static final String IS_BASE_FONT = "IsBaseFont";
  100       /**
  101        * This is the V Vector attribute.
  102        */
  103       public static final String V_VECTOR = "VVector";
  104       /**
  105        * This will tell if the V is fixed.
  106        */
  107       public static final String IS_FIXED_V = "IsFixedV";
  108       /**
  109        * This is the cap height attribute.
  110        */
  111       public static final String CAP_HEIGHT = "CapHeight";
  112       /**
  113        * This is the X height.
  114        */
  115       public static final String X_HEIGHT = "XHeight";
  116       /**
  117        * This is ascender attribute.
  118        */
  119       public static final String ASCENDER = "Ascender";
  120       /**
  121        * This is the descender attribute.
  122        */
  123       public static final String DESCENDER = "Descender";
  124   
  125       /**
  126        * The underline position.
  127        */
  128       public static final String UNDERLINE_POSITION = "UnderlinePosition";
  129       /**
  130        * This is the Underline thickness.
  131        */
  132       public static final String UNDERLINE_THICKNESS = "UnderlineThickness";
  133       /**
  134        * This is the italic angle.
  135        */
  136       public static final String ITALIC_ANGLE = "ItalicAngle";
  137       /**
  138        * This is the char width.
  139        */
  140       public static final String CHAR_WIDTH = "CharWidth";
  141       /**
  142        * This will determine if this is fixed pitch.
  143        */
  144       public static final String IS_FIXED_PITCH = "IsFixedPitch";
  145       /**
  146        * This is the start of character metrics.
  147        */
  148       public static final String START_CHAR_METRICS = "StartCharMetrics";
  149       /**
  150        * This is the end of character metrics.
  151        */
  152       public static final String END_CHAR_METRICS = "EndCharMetrics";
  153       /**
  154        * The character metrics c value.
  155        */
  156       public static final String CHARMETRICS_C = "C";
  157       /**
  158        * The character metrics c value.
  159        */
  160       public static final String CHARMETRICS_CH = "CH";
  161       /**
  162        * The character metrics value.
  163        */
  164       public static final String CHARMETRICS_WX = "WX";
  165       /**
  166        * The character metrics value.
  167        */
  168       public static final String CHARMETRICS_W0X = "W0X";
  169       /**
  170        * The character metrics value.
  171        */
  172       public static final String CHARMETRICS_W1X = "W1X";
  173       /**
  174        * The character metrics value.
  175        */
  176       public static final String CHARMETRICS_WY = "WY";
  177       /**
  178        * The character metrics value.
  179        */
  180       public static final String CHARMETRICS_W0Y = "W0Y";
  181       /**
  182        * The character metrics value.
  183        */
  184       public static final String CHARMETRICS_W1Y = "W1Y";
  185       /**
  186        * The character metrics value.
  187        */
  188       public static final String CHARMETRICS_W = "W";
  189       /**
  190        * The character metrics value.
  191        */
  192       public static final String CHARMETRICS_W0 = "W0";
  193       /**
  194        * The character metrics value.
  195        */
  196       public static final String CHARMETRICS_W1 = "W1";
  197       /**
  198        * The character metrics value.
  199        */
  200       public static final String CHARMETRICS_VV = "VV";
  201       /**
  202        * The character metrics value.
  203        */
  204       public static final String CHARMETRICS_N = "N";
  205       /**
  206        * The character metrics value.
  207        */
  208       public static final String CHARMETRICS_B = "B";
  209       /**
  210        * The character metrics value.
  211        */
  212       public static final String CHARMETRICS_L = "L";
  213       /**
  214        * The character metrics value.
  215        */
  216       public static final String STD_HW = "StdHW";
  217       /**
  218        * The character metrics value.
  219        */
  220       public static final String STD_VW = "StdVW";
  221       /**
  222        * This is the start of track kern data.
  223        */
  224       public static final String START_TRACK_KERN = "StartTrackKern";
  225       /**
  226        * This is the end of track kern data.
  227        */
  228       public static final String END_TRACK_KERN = "EndTrackKern";
  229       /**
  230        * This is the start of kern data.
  231        */
  232       public static final String START_KERN_DATA = "StartKernData";
  233       /**
  234        * This is the end of kern data.
  235        */
  236       public static final String END_KERN_DATA = "EndKernData";
  237       /**
  238        * This is the start of kern pairs data.
  239        */
  240       public static final String START_KERN_PAIRS = "StartKernPairs";
  241       /**
  242        * This is the end of kern pairs data.
  243        */
  244       public static final String END_KERN_PAIRS = "EndKernPairs";
  245       /**
  246        * This is the start of kern pairs data.
  247        */
  248       public static final String START_KERN_PAIRS0 = "StartKernPairs0";
  249       /**
  250        * This is the start of kern pairs data.
  251        */
  252       public static final String START_KERN_PAIRS1 = "StartKernPairs1";
  253       /**
  254        * This is the start compisites data section.
  255        */
  256       public static final String START_COMPOSITES = "StartComposites";
  257       /**
  258        * This is the end compisites data section.
  259        */
  260       public static final String END_COMPOSITES = "EndComposites";
  261       /**
  262        * This is a composite character.
  263        */
  264       public static final String CC = "CC";
  265       /**
  266        * This is a composite charater part.
  267        */
  268       public static final String PCC = "PCC";
  269       /**
  270        * This is a kern pair.
  271        */
  272       public static final String KERN_PAIR_KP = "KP";
  273       /**
  274        * This is a kern pair.
  275        */
  276       public static final String KERN_PAIR_KPH = "KPH";
  277       /**
  278        * This is a kern pair.
  279        */
  280       public static final String KERN_PAIR_KPX = "KPX";
  281       /**
  282        * This is a kern pair.
  283        */
  284       public static final String KERN_PAIR_KPY = "KPY";
  285   
  286       private static final int BITS_IN_HEX = 16;
  287   
  288   
  289       private InputStream input;
  290       private FontMetric result;
  291   
  292       /**
  293        * A method to test parsing of all AFM documents in the resources
  294        * directory.
  295        *
  296        * @param args Ignored.
  297        *
  298        * @throws IOException If there is an error parsing one of the documents.
  299        */
  300       public static void main( String[] args ) throws IOException
  301       {
  302           java.io.File afmDir = new java.io.File( "Resources/afm" );
  303           java.io.File[] files = afmDir.listFiles();
  304           for( int i=0; i< files.length; i++ )
  305           {
  306               if( files[i].getPath().toUpperCase().endsWith( ".AFM" ) )
  307               {
  308                   long start = System.currentTimeMillis();
  309                   java.io.FileInputStream input = new java.io.FileInputStream( files[i] );
  310                   AFMParser parser = new AFMParser( input );
  311                   parser.parse();
  312                   long stop = System.currentTimeMillis();
  313                   System.out.println( "Parsing:" + files[i].getPath() + " " + (stop-start) );
  314               }
  315           }
  316       }
  317   
  318       /**
  319        * Constructor.
  320        *
  321        * @param in The input stream to read the AFM document from.
  322        */
  323       public AFMParser( InputStream in )
  324       {
  325           input = in;
  326       }
  327   
  328       /**
  329        * This will parse the AFM document.  This will close the Input stream
  330        * when the parsing is finished.
  331        *
  332        * @throws IOException If there is an IO error reading the document.
  333        */
  334       public void parse() throws IOException
  335       {
  336           result = parseFontMetric();
  337       }
  338   
  339       /**
  340        * This will get the result of the parsing.
  341        *
  342        * @return The parsed java object.
  343        */
  344       public FontMetric getResult()
  345       {
  346           return result;
  347       }
  348   
  349       /**
  350        * This will parse a font metrics item.
  351        *
  352        * @return The parse font metrics item.
  353        *
  354        * @throws IOException If there is an error reading the AFM file.
  355        */
  356       private FontMetric parseFontMetric() throws IOException
  357       {
  358           FontMetric fontMetrics = new FontMetric();
  359           String startFontMetrics = readString();
  360           if( !START_FONT_METRICS.equals( startFontMetrics ) )
  361           {
  362               throw new IOException( "Error: The AFM file should start with " + START_FONT_METRICS +
  363                                      " and not '" + startFontMetrics + "'" );
  364           }
  365           fontMetrics.setAFMVersion( readFloat() );
  366           String nextCommand = null;
  367           while( !END_FONT_METRICS.equals( (nextCommand = readString() ) ) )
  368           {
  369               if( FONT_NAME.equals( nextCommand ) )
  370               {
  371                   fontMetrics.setFontName( readLine() );
  372               }
  373               else if( FULL_NAME.equals( nextCommand ) )
  374               {
  375                   fontMetrics.setFullName( readLine() );
  376               }
  377               else if( FAMILY_NAME.equals( nextCommand ) )
  378               {
  379                   fontMetrics.setFamilyName( readLine() );
  380               }
  381               else if( WEIGHT.equals( nextCommand ) )
  382               {
  383                   fontMetrics.setWeight( readLine() );
  384               }
  385               else if( FONT_BBOX.equals( nextCommand ) )
  386               {
  387                   BoundingBox bBox = new BoundingBox();
  388                   bBox.setLowerLeftX( readFloat() );
  389                   bBox.setLowerLeftY( readFloat() );
  390                   bBox.setUpperRightX( readFloat() );
  391                   bBox.setUpperRightY( readFloat() );
  392                   fontMetrics.setFontBBox( bBox );
  393               }
  394               else if( VERSION.equals( nextCommand ) )
  395               {
  396                   fontMetrics.setFontVersion( readLine() );
  397               }
  398               else if( NOTICE.equals( nextCommand ) )
  399               {
  400                   fontMetrics.setNotice( readLine() );
  401               }
  402               else if( ENCODING_SCHEME.equals( nextCommand ) )
  403               {
  404                   fontMetrics.setEncodingScheme( readLine() );
  405               }
  406               else if( MAPPING_SCHEME.equals( nextCommand ) )
  407               {
  408                   fontMetrics.setMappingScheme( readInt() );
  409               }
  410               else if( ESC_CHAR.equals( nextCommand ) )
  411               {
  412                   fontMetrics.setEscChar( readInt() );
  413               }
  414               else if( CHARACTER_SET.equals( nextCommand ) )
  415               {
  416                   fontMetrics.setCharacterSet( readLine() );
  417               }
  418               else if( CHARACTERS.equals( nextCommand ) )
  419               {
  420                   fontMetrics.setCharacters( readInt() );
  421               }
  422               else if( IS_BASE_FONT.equals( nextCommand ) )
  423               {
  424                   fontMetrics.setIsBaseFont( readBoolean() );
  425               }
  426               else if( V_VECTOR.equals( nextCommand ) )
  427               {
  428                   float[] vector = new float[2];
  429                   vector[0] = readFloat();
  430                   vector[1] = readFloat();
  431                   fontMetrics.setVVector( vector );
  432               }
  433               else if( IS_FIXED_V.equals( nextCommand ) )
  434               {
  435                   fontMetrics.setIsFixedV( readBoolean() );
  436               }
  437               else if( CAP_HEIGHT.equals( nextCommand ) )
  438               {
  439                   fontMetrics.setCapHeight( readFloat() );
  440               }
  441               else if( X_HEIGHT.equals( nextCommand ) )
  442               {
  443                   fontMetrics.setXHeight( readFloat() );
  444               }
  445               else if( ASCENDER.equals( nextCommand ) )
  446               {
  447                   fontMetrics.setAscender( readFloat() );
  448               }
  449               else if( DESCENDER.equals( nextCommand ) )
  450               {
  451                   fontMetrics.setDescender( readFloat() );
  452               }
  453               else if( STD_HW.equals( nextCommand ) )
  454               {
  455                   fontMetrics.setStandardHorizontalWidth( readFloat() );
  456               }
  457               else if( STD_VW.equals( nextCommand ) )
  458               {
  459                   fontMetrics.setStandardVerticalWidth( readFloat() );
  460               }
  461               else if( COMMENT.equals( nextCommand ) )
  462               {
  463                   fontMetrics.addComment( readLine() );
  464               }
  465               else if( UNDERLINE_POSITION.equals( nextCommand ) )
  466               {
  467                   fontMetrics.setUnderlinePosition( readFloat() );
  468               }
  469               else if( UNDERLINE_THICKNESS.equals( nextCommand ) )
  470               {
  471                   fontMetrics.setUnderlineThickness( readFloat() );
  472               }
  473               else if( ITALIC_ANGLE.equals( nextCommand ) )
  474               {
  475                   fontMetrics.setItalicAngle( readFloat() );
  476               }
  477               else if( CHAR_WIDTH.equals( nextCommand ) )
  478               {
  479                   float[] widths = new float[2];
  480                   widths[0] = readFloat();
  481                   widths[1] = readFloat();
  482                   fontMetrics.setCharWidth( widths );
  483               }
  484               else if( IS_FIXED_PITCH.equals( nextCommand ) )
  485               {
  486                   fontMetrics.setFixedPitch( readBoolean() );
  487               }
  488               else if( START_CHAR_METRICS.equals( nextCommand ) )
  489               {
  490                   int count = readInt();
  491                   for( int i=0; i<count; i++ )
  492                   {
  493                       CharMetric charMetric = parseCharMetric();
  494                       fontMetrics.addCharMetric( charMetric );
  495                   }
  496                   String end = readString();
  497                   if( !end.equals( END_CHAR_METRICS ) )
  498                   {
  499                       throw new IOException( "Error: Expected '" + END_CHAR_METRICS + "' actual '" +
  500                                                   end + "'" );
  501                   }
  502               }
  503               else if( START_COMPOSITES.equals( nextCommand ) )
  504               {
  505                   int count = readInt();
  506                   for( int i=0; i<count; i++ )
  507                   {
  508                       Composite part = parseComposite();
  509                       fontMetrics.addComposite( part );
  510                   }
  511                   String end = readString();
  512                   if( !end.equals( END_COMPOSITES ) )
  513                   {
  514                       throw new IOException( "Error: Expected '" + END_COMPOSITES + "' actual '" +
  515                                                   end + "'" );
  516                   }
  517               }
  518               else if( START_KERN_DATA.equals( nextCommand ) )
  519               {
  520                   parseKernData( fontMetrics );
  521               }
  522               else
  523               {
  524                   throw new IOException( "Unknown AFM key '" + nextCommand + "'" );
  525               }
  526           }
  527           return fontMetrics;
  528       }
  529   
  530       /**
  531        * This will parse the kern data.
  532        *
  533        * @param fontMetrics The metrics class to put the parsed data into.
  534        *
  535        * @throws IOException If there is an error parsing the data.
  536        */
  537       private void parseKernData( FontMetric fontMetrics ) throws IOException
  538       {
  539           String nextCommand = null;
  540           while( !(nextCommand = readString()).equals( END_KERN_DATA ) )
  541           {
  542               if( START_TRACK_KERN.equals( nextCommand ) )
  543               {
  544                   int count = readInt();
  545                   for( int i=0; i<count; i++ )
  546                   {
  547                       TrackKern kern = new TrackKern();
  548                       kern.setDegree( readInt() );
  549                       kern.setMinPointSize( readFloat() );
  550                       kern.setMinKern( readFloat() );
  551                       kern.setMaxPointSize( readFloat() );
  552                       kern.setMaxKern( readFloat() );
  553                       fontMetrics.addTrackKern( kern );
  554                   }
  555                   String end = readString();
  556                   if( !end.equals( END_TRACK_KERN ) )
  557                   {
  558                       throw new IOException( "Error: Expected '" + END_TRACK_KERN + "' actual '" +
  559                                                   end + "'" );
  560                   }
  561               }
  562               else if( START_KERN_PAIRS.equals( nextCommand ) )
  563               {
  564                   int count = readInt();
  565                   for( int i=0; i<count; i++ )
  566                   {
  567                       KernPair pair = parseKernPair();
  568                       fontMetrics.addKernPair( pair );
  569                   }
  570                   String end = readString();
  571                   if( !end.equals( END_KERN_PAIRS ) )
  572                   {
  573                       throw new IOException( "Error: Expected '" + END_KERN_PAIRS + "' actual '" +
  574                                                   end + "'" );
  575                   }
  576               }
  577               else if( START_KERN_PAIRS0.equals( nextCommand ) )
  578               {
  579                   int count = readInt();
  580                   for( int i=0; i<count; i++ )
  581                   {
  582                       KernPair pair = parseKernPair();
  583                       fontMetrics.addKernPair0( pair );
  584                   }
  585                   String end = readString();
  586                   if( !end.equals( END_KERN_PAIRS ) )
  587                   {
  588                       throw new IOException( "Error: Expected '" + END_KERN_PAIRS + "' actual '" +
  589                                                   end + "'" );
  590                   }
  591               }
  592               else if( START_KERN_PAIRS1.equals( nextCommand ) )
  593               {
  594                   int count = readInt();
  595                   for( int i=0; i<count; i++ )
  596                   {
  597                       KernPair pair = parseKernPair();
  598                       fontMetrics.addKernPair1( pair );
  599                   }
  600                   String end = readString();
  601                   if( !end.equals( END_KERN_PAIRS ) )
  602                   {
  603                       throw new IOException( "Error: Expected '" + END_KERN_PAIRS + "' actual '" +
  604                                                   end + "'" );
  605                   }
  606               }
  607               else
  608               {
  609                   throw new IOException( "Unknown kerning data type '" + nextCommand + "'" );
  610               }
  611           }
  612       }
  613   
  614       /**
  615        * This will parse a kern pair from the data stream.
  616        *
  617        * @return The kern pair that was parsed from the stream.
  618        *
  619        * @throws IOException If there is an error reading from the stream.
  620        */
  621       private KernPair parseKernPair() throws IOException
  622       {
  623           KernPair kernPair = new KernPair();
  624           String cmd = readString();
  625           if( KERN_PAIR_KP.equals( cmd ) )
  626           {
  627               String first = readString();
  628               String second = readString();
  629               float x = readFloat();
  630               float y = readFloat();
  631               kernPair.setFirstKernCharacter( first );
  632               kernPair.setSecondKernCharacter( second );
  633               kernPair.setX( x );
  634               kernPair.setY( y );
  635           }
  636           else if( KERN_PAIR_KPH.equals( cmd ) )
  637           {
  638               String first = hexToString( readString() );
  639               String second = hexToString( readString() );
  640               float x = readFloat();
  641               float y = readFloat();
  642               kernPair.setFirstKernCharacter( first );
  643               kernPair.setSecondKernCharacter( second );
  644               kernPair.setX( x );
  645               kernPair.setY( y );
  646           }
  647           else if( KERN_PAIR_KPX.equals( cmd ) )
  648           {
  649               String first = readString();
  650               String second = readString();
  651               float x = readFloat();
  652               kernPair.setFirstKernCharacter( first );
  653               kernPair.setSecondKernCharacter( second );
  654               kernPair.setX( x );
  655               kernPair.setY( 0 );
  656           }
  657           else if( KERN_PAIR_KPY.equals( cmd ) )
  658           {
  659               String first = readString();
  660               String second = readString();
  661               float y = readFloat();
  662               kernPair.setFirstKernCharacter( first );
  663               kernPair.setSecondKernCharacter( second );
  664               kernPair.setX( 0 );
  665               kernPair.setY( y );
  666           }
  667           else
  668           {
  669               throw new IOException( "Error expected kern pair command actual='" + cmd + "'" );
  670           }
  671           return kernPair;
  672       }
  673   
  674       /**
  675        * This will convert and angle bracket hex string to a string.
  676        *
  677        * @param hexString An angle bracket string.
  678        *
  679        * @return The bytes of the hex string.
  680        *
  681        * @throws IOException If the string is in an invalid format.
  682        */
  683       private String hexToString( String hexString ) throws IOException
  684       {
  685           if( hexString.length() < 2 )
  686           {
  687               throw new IOException( "Error: Expected hex string of length >= 2 not='" + hexString );
  688           }
  689           if( hexString.charAt( 0 ) != '<' ||
  690               hexString.charAt( hexString.length() -1 ) != '>' )
  691           {
  692               throw new IOException( "String should be enclosed by angle brackets '" + hexString+ "'" );
  693           }
  694           hexString = hexString.substring( 1, hexString.length() -1 );
  695           byte[] data = new byte[ (hexString.length() / 2) ];
  696           for( int i=0; i<hexString.length(); i+=2 )
  697           {
  698               String hex = "" + hexString.charAt( i ) + hexString.charAt( i+1 );
  699               try
  700               {
  701                   data[ i / 2 ] = (byte)Integer.parseInt( hex, BITS_IN_HEX );
  702               }
  703               catch( NumberFormatException e )
  704               {
  705                   throw new IOException( "Error parsing AFM file:" + e );
  706               }
  707           }
  708           return new String( data );
  709       }
  710   
  711       /**
  712        * This will parse a composite part from the stream.
  713        *
  714        * @return The composite.
  715        *
  716        * @throws IOException If there is an error parsing the composite.
  717        */
  718       private Composite parseComposite() throws IOException
  719       {
  720           Composite composite = new Composite();
  721           String partData = readLine();
  722           StringTokenizer tokenizer = new StringTokenizer( partData, " ;" );
  723   
  724   
  725           String cc = tokenizer.nextToken();
  726           if( !cc.equals( CC ) )
  727           {
  728               throw new IOException( "Expected '" + CC + "' actual='" + cc + "'" );
  729           }
  730           String name = tokenizer.nextToken();
  731           composite.setName( name );
  732   
  733           int partCount;
  734           try
  735           {
  736               partCount = Integer.parseInt( tokenizer.nextToken() );
  737           }
  738           catch( NumberFormatException e )
  739           {
  740               throw new IOException( "Error parsing AFM document:" + e );
  741           }
  742           for( int i=0; i<partCount; i++ )
  743           {
  744               CompositePart part = new CompositePart();
  745               String pcc = tokenizer.nextToken();
  746               if( !pcc.equals( PCC ) )
  747               {
  748                   throw new IOException( "Expected '" + PCC + "' actual='" + pcc + "'" );
  749               }
  750               String partName = tokenizer.nextToken();
  751               try
  752               {
  753                   int x = Integer.parseInt( tokenizer.nextToken() );
  754                   int y = Integer.parseInt( tokenizer.nextToken() );
  755   
  756                   part.setName( partName );
  757                   part.setXDisplacement( x );
  758                   part.setYDisplacement( y );
  759                   composite.addPart( part );
  760               }
  761               catch( NumberFormatException e )
  762               {
  763                   throw new IOException( "Error parsing AFM document:" + e );
  764               }
  765           }
  766           return composite;
  767       }
  768   
  769       /**
  770        * This will parse a single CharMetric object from the stream.
  771        *
  772        * @return The next char metric in the stream.
  773        *
  774        * @throws IOException If there is an error reading from the stream.
  775        */
  776       private CharMetric parseCharMetric() throws IOException
  777       {
  778           CharMetric charMetric = new CharMetric();
  779           String metrics = readLine();
  780           StringTokenizer metricsTokenizer = new StringTokenizer( metrics );
  781           try
  782           {
  783               while( metricsTokenizer.hasMoreTokens() )
  784               {
  785                   String nextCommand = metricsTokenizer.nextToken();
  786                   if( nextCommand.equals( CHARMETRICS_C ) )
  787                   {
  788                       String charCode = metricsTokenizer.nextToken();
  789                       charMetric.setCharacterCode( Integer.parseInt( charCode ) );
  790                       verifySemicolon( metricsTokenizer );
  791                   }
  792                   else if( nextCommand.equals( CHARMETRICS_CH ) )
  793                   {
  794                       //Is the hex string <FF> or FF, the spec is a little
  795                       //unclear, wait and see if it breaks anything.
  796                       String charCode = metricsTokenizer.nextToken();
  797                       charMetric.setCharacterCode( Integer.parseInt( charCode, BITS_IN_HEX ) );
  798                       verifySemicolon( metricsTokenizer );
  799                   }
  800                   else if( nextCommand.equals( CHARMETRICS_WX ) )
  801                   {
  802                       String wx = metricsTokenizer.nextToken();
  803                       charMetric.setWx( Float.parseFloat( wx ) );
  804                       verifySemicolon( metricsTokenizer );
  805                   }
  806                   else if( nextCommand.equals( CHARMETRICS_W0X ) )
  807                   {
  808                       String w0x = metricsTokenizer.nextToken();
  809                       charMetric.setW0x( Float.parseFloat( w0x ) );
  810                       verifySemicolon( metricsTokenizer );
  811                   }
  812                   else if( nextCommand.equals( CHARMETRICS_W1X ) )
  813                   {
  814                       String w1x = metricsTokenizer.nextToken();
  815                       charMetric.setW0x( Float.parseFloat( w1x ) );
  816                       verifySemicolon( metricsTokenizer );
  817                   }
  818                   else if( nextCommand.equals( CHARMETRICS_WY ) )
  819                   {
  820                       String wy = metricsTokenizer.nextToken();
  821                       charMetric.setWy( Float.parseFloat( wy ) );
  822                       verifySemicolon( metricsTokenizer );
  823                   }
  824                   else if( nextCommand.equals( CHARMETRICS_W0Y ) )
  825                   {
  826                       String w0y = metricsTokenizer.nextToken();
  827                       charMetric.setW0y( Float.parseFloat( w0y ) );
  828                       verifySemicolon( metricsTokenizer );
  829                   }
  830                   else if( nextCommand.equals( CHARMETRICS_W1Y ) )
  831                   {
  832                       String w1y = metricsTokenizer.nextToken();
  833                       charMetric.setW0y( Float.parseFloat( w1y ) );
  834                       verifySemicolon( metricsTokenizer );
  835                   }
  836                   else if( nextCommand.equals( CHARMETRICS_W ) )
  837                   {
  838                       String w0 = metricsTokenizer.nextToken();
  839                       String w1 = metricsTokenizer.nextToken();
  840                       float[] w = new float[2];
  841                       w[0] = Float.parseFloat( w0 );
  842                       w[1] = Float.parseFloat( w1 );
  843                       charMetric.setW( w );
  844                       verifySemicolon( metricsTokenizer );
  845                   }
  846                   else if( nextCommand.equals( CHARMETRICS_W0 ) )
  847                   {
  848                       String w00 = metricsTokenizer.nextToken();
  849                       String w01 = metricsTokenizer.nextToken();
  850                       float[] w0 = new float[2];
  851                       w0[0] = Float.parseFloat( w00 );
  852                       w0[1] = Float.parseFloat( w01 );
  853                       charMetric.setW0( w0 );
  854                       verifySemicolon( metricsTokenizer );
  855                   }
  856                   else if( nextCommand.equals( CHARMETRICS_W1 ) )
  857                   {
  858                       String w10 = metricsTokenizer.nextToken();
  859                       String w11 = metricsTokenizer.nextToken();
  860                       float[] w1 = new float[2];
  861                       w1[0] = Float.parseFloat( w10 );
  862                       w1[1] = Float.parseFloat( w11 );
  863                       charMetric.setW1( w1 );
  864                       verifySemicolon( metricsTokenizer );
  865                   }
  866                   else if( nextCommand.equals( CHARMETRICS_VV ) )
  867                   {
  868                       String vv0 = metricsTokenizer.nextToken();
  869                       String vv1 = metricsTokenizer.nextToken();
  870                       float[] vv = new float[2];
  871                       vv[0] = Float.parseFloat( vv0 );
  872                       vv[1] = Float.parseFloat( vv1 );
  873                       charMetric.setVv( vv );
  874                       verifySemicolon( metricsTokenizer );
  875                   }
  876                   else if( nextCommand.equals( CHARMETRICS_N ) )
  877                   {
  878                       String name = metricsTokenizer.nextToken();
  879                       charMetric.setName( name );
  880                       verifySemicolon( metricsTokenizer );
  881                   }
  882                   else if( nextCommand.equals( CHARMETRICS_B ) )
  883                   {
  884                       String llx = metricsTokenizer.nextToken();
  885                       String lly = metricsTokenizer.nextToken();
  886                       String urx = metricsTokenizer.nextToken();
  887                       String ury = metricsTokenizer.nextToken();
  888                       BoundingBox box = new BoundingBox();
  889                       box.setLowerLeftX( Float.parseFloat( llx ) );
  890                       box.setLowerLeftY( Float.parseFloat( lly ) );
  891                       box.setUpperRightX( Float.parseFloat( urx ) );
  892                       box.setUpperRightY( Float.parseFloat( ury ) );
  893                       charMetric.setBoundingBox( box );
  894                       verifySemicolon( metricsTokenizer );
  895                   }
  896                   else if( nextCommand.equals( CHARMETRICS_L ) )
  897                   {
  898                       String successor = metricsTokenizer.nextToken();
  899                       String ligature = metricsTokenizer.nextToken();
  900                       Ligature lig = new Ligature();
  901                       lig.setSuccessor( successor );
  902                       lig.setLigature( ligature );
  903                       charMetric.addLigature( lig );
  904                       verifySemicolon( metricsTokenizer );
  905                   }
  906                   else
  907                   {
  908                       throw new IOException( "Unknown CharMetrics command '" + nextCommand + "'" );
  909                   }
  910               }
  911           }
  912           catch( NumberFormatException e )
  913           {
  914               throw new IOException( "Error: Corrupt AFM document:"  + e );
  915           }
  916           return charMetric;
  917       }
  918   
  919       /**
  920        * This is used to verify that a semicolon is the next token in the stream.
  921        *
  922        * @param tokenizer The tokenizer to read from.
  923        *
  924        * @throws IOException If the semicolon is missing.
  925        */
  926       private void verifySemicolon( StringTokenizer tokenizer ) throws IOException
  927       {
  928           if( tokenizer.hasMoreTokens() )
  929           {
  930               String semicolon = tokenizer.nextToken();
  931               if( !semicolon.equals( ";" ) )
  932               {
  933                   throw new IOException( "Error: Expected semicolon in stream actual='" +
  934                                               semicolon + "'" );
  935               }
  936           }
  937           else
  938           {
  939               throw new IOException( "CharMetrics is missing a semicolon after a command" );
  940           }
  941       }
  942   
  943       /**
  944        * This will read a boolean from the stream.
  945        *
  946        * @return The boolean in the stream.
  947        */
  948       private boolean readBoolean() throws IOException
  949       {
  950           String theBoolean = readString();
  951           return Boolean.valueOf( theBoolean ).booleanValue();
  952       }
  953   
  954       /**
  955        * This will read an integer from the stream.
  956        *
  957        * @return The integer in the stream.
  958        */
  959       private int readInt() throws IOException
  960       {
  961           String theInt = readString();
  962           try
  963           {
  964               return Integer.parseInt( theInt );
  965           }
  966           catch( NumberFormatException e )
  967           {
  968               throw new IOException( "Error parsing AFM document:" + e );
  969           }
  970       }
  971   
  972       /**
  973        * This will read a float from the stream.
  974        *
  975        * @return The float in the stream.
  976        */
  977       private float readFloat() throws IOException
  978       {
  979           String theFloat = readString();
  980           return Float.parseFloat( theFloat );
  981       }
  982   
  983       /**
  984        * This will read until the end of a line.
  985        *
  986        * @return The string that is read.
  987        */
  988       private String readLine() throws IOException
  989       {
  990           //First skip the whitespace
  991           StringBuffer buf = new StringBuffer();
  992           int nextByte = input.read();
  993           while( isWhitespace( nextByte ) )
  994           {
  995               nextByte = input.read();
  996               //do nothing just skip the whitespace.
  997           }
  998           buf.append( (char)nextByte );
  999   
 1000           //now read the data
 1001           while( !isEOL(nextByte = input.read()) )
 1002           {
 1003               buf.append( (char)nextByte );
 1004           }
 1005           return buf.toString();
 1006       }
 1007   
 1008       /**
 1009        * This will read a string from the input stream and stop at any whitespace.
 1010        *
 1011        * @return The string read from the stream.
 1012        *
 1013        * @throws IOException If an IO error occurs when reading from the stream.
 1014        */
 1015       private String readString() throws IOException
 1016       {
 1017           //First skip the whitespace
 1018           StringBuffer buf = new StringBuffer();
 1019           int nextByte = input.read();
 1020           while( isWhitespace( nextByte ) )
 1021           {
 1022               nextByte = input.read();
 1023               //do nothing just skip the whitespace.
 1024           }
 1025           buf.append( (char)nextByte );
 1026   
 1027           //now read the data
 1028           while( !isWhitespace(nextByte = input.read()) )
 1029           {
 1030               buf.append( (char)nextByte );
 1031           }
 1032           return buf.toString();
 1033       }
 1034   
 1035       /**
 1036        * This will determine if the byte is a whitespace character or not.
 1037        *
 1038        * @param character The character to test for whitespace.
 1039        *
 1040        * @return true If the character is whitespace as defined by the AFM spec.
 1041        */
 1042       private boolean isEOL( int character )
 1043       {
 1044           return character == 0x0D ||
 1045                  character == 0x0A;
 1046       }
 1047   
 1048       /**
 1049        * This will determine if the byte is a whitespace character or not.
 1050        *
 1051        * @param character The character to test for whitespace.
 1052        *
 1053        * @return true If the character is whitespace as defined by the AFM spec.
 1054        */
 1055       private boolean isWhitespace( int character )
 1056       {
 1057           return character == ' ' ||
 1058                  character == '\t' ||
 1059                  character == 0x0D ||
 1060                  character == 0x0A;
 1061       }
 1062   }

Home » pdfbox-1.1.0-src » org.apache.fontbox.afm » [javadoc | source]