1 31 package org.pdfbox.pdmodel.font; 32 33 import org.fontbox.afm.AFMParser; 34 35 import org.fontbox.afm.FontMetric; 36 37 import org.fontbox.cmap.CMapParser; 38 39 import org.fontbox.cmap.CMap; 40 41 import org.pdfbox.encoding.AFMEncoding; 42 import org.pdfbox.encoding.DictionaryEncoding; 43 import org.pdfbox.encoding.Encoding; 44 import org.pdfbox.encoding.EncodingManager; 45 46 import org.pdfbox.cos.COSArray; 47 import org.pdfbox.cos.COSBase; 48 import org.pdfbox.cos.COSDictionary; 49 import org.pdfbox.cos.COSFloat; 50 import org.pdfbox.cos.COSName; 51 import org.pdfbox.cos.COSNumber; 52 import org.pdfbox.cos.COSStream; 53 54 import org.pdfbox.pdmodel.common.COSArrayList; 55 import org.pdfbox.pdmodel.common.COSObjectable; 56 import org.pdfbox.pdmodel.common.PDMatrix; 57 import org.pdfbox.pdmodel.common.PDRectangle; 58 59 import org.pdfbox.util.ResourceLoader; 60 61 import java.awt.Graphics ; 62 63 import java.io.BufferedReader ; 64 import java.io.InputStream ; 65 import java.io.InputStreamReader ; 66 import java.io.IOException ; 67 68 import java.util.Collections ; 69 import java.util.HashMap ; 70 import java.util.List ; 71 import java.util.Map ; 72 import java.util.StringTokenizer ; 73 74 80 public abstract class PDFont implements COSObjectable 81 { 82 83 86 protected COSDictionary font; 87 88 91 private Encoding fontEncoding = null; 92 96 private CMap cmap = null; 97 98 private static Map afmResources = null; 99 private static Map cmapObjects = null; 100 private static Map afmObjects = null; 101 private static Map cmapSubstitutions = null; 102 103 static 104 { 105 afmResources = new HashMap (); 107 cmapSubstitutions = new HashMap (); 108 109 cmapObjects = Collections.synchronizedMap( new HashMap () ); 111 afmObjects = Collections.synchronizedMap( new HashMap () ); 112 113 114 afmResources.put( COSName.getPDFName( "Courier-Bold" ), "Resources/afm/Courier-Bold.afm" ); 115 afmResources.put( COSName.getPDFName( "Courier-BoldOblique" ), "Resources/afm/Courier-BoldOblique.afm" ); 116 afmResources.put( COSName.getPDFName( "Courier" ), "Resources/afm/Courier.afm" ); 117 afmResources.put( COSName.getPDFName( "Courier-Oblique" ), "Resources/afm/Courier-Oblique.afm" ); 118 afmResources.put( COSName.getPDFName( "Helvetica" ), "Resources/afm/Helvetica.afm" ); 119 afmResources.put( COSName.getPDFName( "Helvetica-Bold" ), "Resources/afm/Helvetica-Bold.afm" ); 120 afmResources.put( COSName.getPDFName( "Helvetica-BoldOblique" ), "Resources/afm/Helvetica-BoldOblique.afm" ); 121 afmResources.put( COSName.getPDFName( "Helvetica-Oblique" ), "Resources/afm/Helvetica-Oblique.afm" ); 122 afmResources.put( COSName.getPDFName( "Symbol" ), "Resources/afm/Symbol.afm" ); 123 afmResources.put( COSName.getPDFName( "Times-Bold" ), "Resources/afm/Times-Bold.afm" ); 124 afmResources.put( COSName.getPDFName( "Times-BoldItalic" ), "Resources/afm/Times-BoldItalic.afm" ); 125 afmResources.put( COSName.getPDFName( "Times-Italic" ), "Resources/afm/Times-Italic.afm" ); 126 afmResources.put( COSName.getPDFName( "Times-Roman" ), "Resources/afm/Times-Roman.afm" ); 127 afmResources.put( COSName.getPDFName( "ZapfDingbats" ), "Resources/afm/ZapfDingbats.afm" ); 128 129 cmapSubstitutions.put( "ETen-B5-H", "ETen-B5-UCS2" ); 130 cmapSubstitutions.put( "ETen-B5-V", "ETen-B5-UCS2" ); 131 cmapSubstitutions.put( "ETenms-B5-H", "ETen-B5-UCS2" ); 132 cmapSubstitutions.put( "ETenms-B5-V", "ETen-B5-UCS2" ); 133 134 cmapSubstitutions.put( "90ms-RKSJ-H", "90ms-RKSJ-UCS2" ); 135 cmapSubstitutions.put( "90ms-RKSJ-V", "90ms-RKSJ-UCS2" ); 136 cmapSubstitutions.put( "90msp-RKSJ-H", "90ms-RKSJ-UCS2" ); 137 cmapSubstitutions.put( "90msp-RKSJ-V", "90ms-RKSJ-UCS2" ); 138 cmapSubstitutions.put( "GBK-EUC-H", "GBK-EUC-UCS2" ); 139 cmapSubstitutions.put( "GBK-EUC-V", "GBK-EUC-UCS2" ); 140 cmapSubstitutions.put( "GBpc-EUC-H", "GBpc-EUC-UCS2C" ); 141 cmapSubstitutions.put( "GBpc-EUC-V", "GBpc-EUC-UCS2C" ); 142 143 cmapSubstitutions.put( "UniJIS-UCS2-HW-H", "UniJIS-UCS2-H" ); 144 } 145 146 157 public static void clearResources() 158 { 159 afmObjects.clear(); 160 cmapObjects.clear(); 161 } 162 163 166 public PDFont() 167 { 168 font = new COSDictionary(); 169 font.setItem( COSName.TYPE, COSName.FONT ); 170 } 171 172 177 public PDFont( COSDictionary fontDictionary ) 178 { 179 font = fontDictionary; 180 } 181 182 185 public COSBase getCOSObject() 186 { 187 return font; 188 } 189 190 201 public abstract float getFontWidth( byte[] c, int offset, int length ) throws IOException ; 202 203 214 public abstract float getFontHeight( byte[] c, int offset, int length ) throws IOException ; 215 216 225 public float getStringWidth( String string ) throws IOException 226 { 227 byte[] data = string.getBytes(); 228 float totalWidth = 0; 229 for( int i=0; i<data.length; i++ ) 230 { 231 totalWidth+=getFontWidth( data, i, 1 ); 232 } 233 return totalWidth; 234 } 235 236 243 public abstract float getAverageFontWidth() throws IOException ; 244 245 258 public abstract void drawString( String string, Graphics g, float fontSize, 259 float xScale, float yScale, float x, float y ) throws IOException ; 260 261 270 protected int getCodeFromArray( byte[] data, int offset, int length ) 271 { 272 int code = 0; 273 for( int i=0; i<length; i++ ) 274 { 275 code <<= 8; 276 code |= (data[offset+i]+256)%256; 277 } 278 return code; 279 } 280 281 290 protected float getFontWidthFromAFMFile( int code ) throws IOException 291 { 292 float retval = 0; 293 FontMetric metric = getAFM(); 294 if( metric != null ) 295 { 296 Encoding encoding = getEncoding(); 297 COSName characterName = encoding.getName( code ); 298 retval = metric.getCharacterWidth( characterName.getName() ); 299 } 300 return retval; 301 } 302 303 310 protected float getAverageFontWidthFromAFMFile() throws IOException 311 { 312 float retval = 0; 313 FontMetric metric = getAFM(); 314 if( metric != null ) 315 { 316 retval = metric.getAverageCharacterWidth(); 317 } 318 return retval; 319 } 320 321 328 protected FontMetric getAFM() throws IOException 329 { 330 COSName name = (COSName)font.getDictionaryObject( COSName.BASE_FONT ); 331 FontMetric result = null; 332 if( name != null ) 333 { 334 result = (FontMetric)afmObjects.get( name ); 335 if( result == null ) 336 { 337 String resource = (String )afmResources.get( name ); 338 if( resource == null ) 339 { 340 } 343 else 344 { 345 InputStream afmStream = ResourceLoader.loadResource( resource ); 346 if( afmStream == null ) 347 { 348 throw new IOException ( "Can't handle font width:" + resource ); 349 } 350 AFMParser parser = new AFMParser( afmStream ); 351 parser.parse(); 352 result = parser.getResult(); 353 afmObjects.put( name, result ); 354 } 355 } 356 } 357 return result; 358 } 359 360 371 public String encode( byte[] c, int offset, int length ) throws IOException 372 { 373 String retval = null; 374 COSName fontSubtype = (COSName)font.getDictionaryObject( COSName.SUBTYPE ); 375 String fontSubtypeName = fontSubtype.getName(); 376 if( fontSubtypeName.equals( "Type0" ) || 377 fontSubtypeName.equals( "Type1" ) || 378 fontSubtypeName.equals( "TrueType" )) 379 { 380 if( cmap == null ) 381 { 382 if( font.getDictionaryObject( COSName.TO_UNICODE ) != null ) 383 { 384 COSStream toUnicode = (COSStream)font.getDictionaryObject( COSName.TO_UNICODE ); 385 if( toUnicode != null ) 386 { 387 parseCmap( toUnicode.getUnfilteredStream(), null ); 388 } 389 } 390 else 391 { 392 COSBase encoding = font.getDictionaryObject( COSName.ENCODING ); 393 if( encoding instanceof COSStream ) 394 { 395 COSStream encodingStream = (COSStream)encoding; 396 parseCmap( encodingStream.getUnfilteredStream(), null ); 397 } 398 else if( fontSubtypeName.equals( "Type0" ) && 399 encoding instanceof COSName ) 400 { 401 COSName encodingName = (COSName)encoding; 402 cmap = (CMap)cmapObjects.get( encodingName ); 403 if( cmap != null ) 404 { 405 cmap = (CMap)cmapObjects.get( encodingName ); 406 } 407 else 408 { 409 String cmapName = encodingName.getName(); 410 cmapName = performCMAPSubstitution( cmapName ); 411 String resourceName = "Resources/cmap/" + cmapName; 412 parseCmap( ResourceLoader.loadResource( resourceName ), encodingName ); 413 if( cmap == null && !encodingName.getName().equals( COSName.IDENTITY_H.getName() ) ) 414 { 415 throw new IOException ( "Error: Could not find predefined " + 416 "CMAP file for '" + encodingName.getName() + "'" ); 417 } 418 } 419 } 420 else if( encoding instanceof COSName || 421 encoding instanceof COSDictionary ) 422 { 423 Encoding currentFontEncoding = getEncoding(); 424 if( currentFontEncoding != null ) 425 { 426 retval = currentFontEncoding.getCharacter( getCodeFromArray( c, offset, length ) ); 427 } 428 } 429 else 430 { 431 COSDictionary fontDescriptor = 432 (COSDictionary)font.getDictionaryObject( COSName.FONT_DESC ); 433 if( fontSubtypeName.equals( "TrueType" ) && 434 fontDescriptor != null && 435 (fontDescriptor.getDictionaryObject( COSName.FONT_FILE )!= null || 436 fontDescriptor.getDictionaryObject( COSName.FONT_FILE2 ) != null || 437 fontDescriptor.getDictionaryObject( COSName.FONT_FILE3 ) != null ) ) 438 { 439 retval = getStringFromArray( c, offset, length ); 443 } 444 else 445 { 446 } 448 } 449 } 450 451 452 } 453 } 454 if( retval == null && cmap != null ) 455 { 456 retval = cmap.lookup( c, offset, length ); 457 } 458 if( retval == null && 463 length == 1 && 464 (cmap == null || !cmap.hasTwoByteMappings())) 465 { 466 Encoding encoding = getEncoding(); 467 if( encoding != null ) 468 { 469 retval = encoding.getCharacter( getCodeFromArray( c, offset, length ) ); 470 } 471 if( retval == null ) 472 { 473 retval = getStringFromArray( c, offset, length ); 474 } 475 } 476 return retval; 477 } 478 479 private static final String [] SINGLE_CHAR_STRING = new String [256]; 480 private static final String [][] DOUBLE_CHAR_STRING = new String [256][256]; 481 static 482 { 483 for( int i=0; i<256; i++ ) 484 { 485 SINGLE_CHAR_STRING[i] = new String ( new byte[] {(byte)i} ); 486 for( int j=0; j<256; j++ ) 487 { 488 DOUBLE_CHAR_STRING[i][j] = new String ( new byte[] {(byte)i, (byte)j} ); 489 } 490 } 491 } 492 493 private static String getStringFromArray( byte[] c, int offset, int length ) throws IOException 494 { 495 String retval = null; 496 if( length == 1 ) 497 { 498 retval = SINGLE_CHAR_STRING[(c[offset]+256)%256]; 499 } 500 else if( length == 2 ) 501 { 502 retval = DOUBLE_CHAR_STRING[(c[offset]+256)%256][(c[offset+1]+256)%256]; 503 } 504 else 505 { 506 throw new IOException ( "Error:Unknown character length:" + length ); 507 } 508 return retval; 509 } 510 511 519 private String performCMAPSubstitution( String cmapName ) 520 { 521 String retval = (String )cmapSubstitutions.get( cmapName ); 522 if( retval == null ) 523 { 524 retval = cmapName; 526 } 527 return retval; 528 } 529 530 private void parseCmap( InputStream cmapStream, COSName encodingName ) throws IOException 531 { 532 if( cmapStream != null ) 533 { 534 CMapParser parser = new CMapParser(); 535 cmap = parser.parse( cmapStream ); 536 if( encodingName != null ) 537 { 538 cmapObjects.put( encodingName, cmap ); 539 } 540 } 541 } 542 543 548 public void setEncoding( Encoding enc ) 549 { 550 font.setItem( COSName.ENCODING, enc ); 551 fontEncoding = enc; 552 } 553 554 563 public Encoding getEncoding() throws IOException 564 { 565 EncodingManager manager = new EncodingManager(); 566 if( fontEncoding == null ) 567 { 568 COSBase encoding = font.getDictionaryObject( COSName.ENCODING ); 569 if( encoding == null ) 570 { 571 FontMetric metric = getAFM(); 572 if( metric != null ) 573 { 574 fontEncoding = new AFMEncoding( metric ); 575 } 576 if( fontEncoding == null ) 577 { 578 fontEncoding = manager.getStandardEncoding(); 579 } 580 } 581 589 else if( encoding instanceof COSDictionary ) 590 { 591 COSDictionary encodingDic = (COSDictionary)encoding; 592 COSName baseEncodingName = (COSName) encodingDic.getDictionaryObject( 596 COSName.BASE_ENCODING); 597 if( baseEncodingName == null) 600 { 601 COSName fontEncodingFromFile = getEncodingFromFont(); 602 encodingDic.setItem( 603 COSName.BASE_ENCODING, 604 fontEncodingFromFile ); 605 } 606 fontEncoding = new DictionaryEncoding( encodingDic ); 607 } 608 else if( encoding instanceof COSName ) 609 { 610 if( !encoding.equals( COSName.IDENTITY_H ) ) 611 { 612 fontEncoding = manager.getEncoding( (COSName)encoding ); 613 } 614 } 615 else 616 { 617 throw new IOException ( "Unexpected encoding type:" + encoding.getClass().getName() ); 618 } 619 } 620 return fontEncoding; 621 } 622 623 628 public String getType() 629 { 630 return font.getNameAsString( COSName.TYPE ); 631 } 632 633 638 public String getSubType() 639 { 640 return font.getNameAsString( COSName.SUBTYPE ); 641 } 642 643 648 public String getBaseFont() 649 { 650 return font.getNameAsString( COSName.BASE_FONT ); 651 } 652 653 658 public void setBaseFont( String baseFont ) 659 { 660 font.setName( COSName.BASE_FONT, baseFont ); 661 } 662 663 668 public int getFirstChar() 669 { 670 return font.getInt( COSName.FIRST_CHAR, -1 ); 671 } 672 673 678 public void setFirstChar( int firstChar ) 679 { 680 font.setInt( COSName.FIRST_CHAR, firstChar ); 681 } 682 683 688 public int getLastChar() 689 { 690 return font.getInt( COSName.LAST_CHAR, -1 ); 691 } 692 693 698 public void setLastChar( int lastChar ) 699 { 700 font.setInt( COSName.LAST_CHAR, lastChar ); 701 } 702 703 708 public List getWidths() 709 { 710 COSArray array = (COSArray)font.getDictionaryObject( COSName.WIDTHS ); 711 return COSArrayList.convertFloatCOSArrayToList( array ); 712 } 713 714 719 public void setWidths( List widths ) 720 { 721 font.setItem( COSName.WIDTHS, COSArrayList.converterToCOSArray( widths ) ); 722 } 723 724 734 public PDMatrix getFontMatrix() 735 { 736 PDMatrix matrix = null; 737 COSArray array = (COSArray)font.getDictionaryObject( COSName.FONT_MATRIX ); 738 if( array == null ) 739 { 740 array = new COSArray(); 741 array.add( new COSFloat( 0.001f ) ); 742 array.add( COSNumber.ZERO ); 743 array.add( COSNumber.ZERO ); 744 array.add( new COSFloat( 0.001f ) ); 745 array.add( COSNumber.ZERO ); 746 array.add( COSNumber.ZERO ); 747 } 748 matrix = new PDMatrix(array); 749 750 return matrix; 751 } 752 753 763 private COSName getEncodingFromFont() throws IOException 764 { 765 768 769 COSName retvalue = null; 770 COSDictionary fontDescriptor = (COSDictionary) font.getDictionaryObject( 777 COSName.FONT_DESC); 778 if( fontDescriptor != null ) 779 { 780 COSStream fontFile = (COSStream) fontDescriptor.getDictionaryObject( 781 COSName.FONT_FILE); 782 if( fontFile != null ) 783 { 784 BufferedReader in = 785 new BufferedReader (new InputStreamReader (fontFile.getUnfilteredStream())); 786 790 StringTokenizer st = null; 791 boolean found = false; 792 String line = ""; 793 String key = null; 794 for( int i = 0; null!=( line = in.readLine() ) && 795 i < 40 && 796 !line.equals("currentdict end") 797 && !found; i++) 798 { 799 st = new StringTokenizer (line); 800 if( st.hasMoreTokens() ) 801 { 802 key = st.nextToken(); 803 if(key.equals("/Encoding") && st.hasMoreTokens() ) 804 { 805 COSName value = COSName.getPDFName( st.nextToken() ); 806 found = true; 807 if( value.equals( COSName.MAC_ROMAN_ENCODING ) || 808 value.equals( COSName.PDF_DOC_ENCODING ) || 809 value.equals( COSName.STANDARD_ENCODING ) || 810 value.equals( COSName.WIN_ANSI_ENCODING ) ) 811 { 812 retvalue = value; 815 } 816 } 817 } 818 } 819 } 820 } 821 return retvalue; 822 } 823 824 831 public abstract PDRectangle getFontBoundingBox() throws IOException ; 832 833 836 public boolean equals( Object other ) 837 { 838 return other instanceof PDFont && ((PDFont)other).getCOSObject() == this.getCOSObject(); 839 } 840 841 844 public int hashCode() 845 { 846 return this.getCOSObject().hashCode(); 847 } 848 }
| Popular Tags
|