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 }