1 26 27 package it.stefanochizzolini.clown.documents.contents.fonts; 28 29 import it.stefanochizzolini.clown.bytes.Buffer; 30 import it.stefanochizzolini.clown.bytes.IInputStream; 31 import it.stefanochizzolini.clown.documents.Document; 32 import it.stefanochizzolini.clown.objects.PdfArray; 33 import it.stefanochizzolini.clown.objects.PdfDictionary; 34 import it.stefanochizzolini.clown.objects.PdfDirectObject; 35 import it.stefanochizzolini.clown.objects.PdfIndirectObject; 36 import it.stefanochizzolini.clown.objects.PdfInteger; 37 import it.stefanochizzolini.clown.objects.PdfName; 38 import it.stefanochizzolini.clown.objects.PdfReal; 39 import it.stefanochizzolini.clown.objects.PdfRectangle; 40 import it.stefanochizzolini.clown.objects.PdfStream; 41 import it.stefanochizzolini.clown.util.NotImplementedException; 42 43 import java.util.EnumSet ; 44 import java.util.Hashtable ; 45 46 49 public class OpenTypeFont 50 extends Font 51 { 52 57 private static final class FontMetrics 58 { 59 62 public boolean isCustomEncoding; 63 66 public float unitNorm; 67 70 public int flags; public int unitsPerEm; public short xMin; 73 public short yMin; 74 public short xMax; 75 public short yMax; 76 public int macStyle; 80 public short ascender; 81 public short descender; 82 public short lineGap; 83 public int advanceWidthMax; public short minLeftSideBearing; 85 public short minRightSideBearing; 86 public short xMaxExtent; 87 public short caretSlopeRise; 88 public short caretSlopeRun; 89 public int numberOfHMetrics; 93 public short sTypoAscender; 94 public short sTypoDescender; 95 public short sTypoLineGap; 96 public short sxHeight; 97 public short sCapHeight; 98 101 public float italicAngle; 102 public short underlinePosition; 103 public short underlineThickness; 104 public boolean isFixedPitch; 105 } 106 107 110 private final class Parser 111 { 112 private static final String Encoding_Latin1 = "ISO-8859-1"; 113 private static final String Encoding_Unicode = "UTF-16"; 114 private static final int MicrosoftLanguage_UsEnglish = 0x409; 115 private static final int NameID_FontPostscriptName = 6; 116 private static final int PlatformID_Microsoft = 3; 117 118 public String fontName; 119 public PdfName subType; 120 121 private IInputStream fontData; 122 123 private Hashtable <String ,Integer > tableOffsets; 124 125 private Parser( 126 IInputStream fontData 127 ) 128 { 129 this.fontData = fontData; 130 131 load(); 132 } 133 134 137 private void load( 138 ) 139 { 140 OpenTypeFont.this.metrics = new FontMetrics(); 141 142 try 143 { 144 switch(fontData.readInt()) 147 { 148 case(0x00010000): this.subType = PdfName.TrueType; 150 break; 151 case(0x4F54544F): this.subType = PdfName.Type1; 153 break; 154 default: 155 throw new FontFileFormatException("Not a valid OpenType font file."); 156 } 157 int tableCount = fontData.readUnsignedShort(); 159 this.tableOffsets = new Hashtable <String ,Integer >(tableCount); 160 161 fontData.skip(6); 163 for( 166 int index = 0; 167 index < tableCount; 168 index++ 169 ) 170 { 171 String tag = readString_ascii(4); 173 fontData.skip(4); 175 int offset = fontData.readInt(); 177 tableOffsets.put(tag,offset); 179 180 fontData.skip(4); 182 } 183 184 this.fontName = load_getName(NameID_FontPostscriptName); 185 186 load_tables(); 187 load_cMap(); 188 load_glyphWidths(); 189 load_glyphKerning(); 190 } 191 catch(Exception e) 192 {throw new RuntimeException (e);} 193 } 194 195 198 private void load_cMap( 199 ) 200 throws FontFileFormatException 201 { 202 213 int tableOffset = tableOffsets.get("cmap"); 216 if(tableOffset == 0) 217 throw new FontFileFormatException("'cmap' table does NOT exist."); 218 219 int map10Offset = 0; 220 int map31Offset = 0; 221 try 222 { 223 fontData.seek(tableOffset + 2); 226 int tableCount = fontData.readUnsignedShort(); 227 228 233 metrics.isCustomEncoding = false; 234 for( 236 int tableIndex = 0; 237 tableIndex < tableCount; 238 tableIndex++ 239 ) 240 { 241 int platformID = fontData.readUnsignedShort(); 243 int encodingID = fontData.readUnsignedShort(); 245 int offset = fontData.readInt(); 247 switch(platformID) 249 { 250 case 1: if(encodingID == 0) 252 map10Offset = offset; 253 break; 254 case PlatformID_Microsoft: 255 261 switch(encodingID) 262 { 263 case 0: metrics.isCustomEncoding = true; 265 break; 266 case 1: map31Offset = offset; 268 break; 269 } 270 break; 271 } 272 } 273 } 274 catch(Exception e) 275 {throw new RuntimeException (e);} 276 277 if(metrics.isCustomEncoding) { 280 if(map10Offset > 0) 282 { 283 fontData.seek(tableOffset + map10Offset); 285 } 286 else 287 {throw new FontFileFormatException("(1,0) symbolic table does NOT exist.");} 288 } 289 else { 291 if(map31Offset > 0) 293 { 294 fontData.seek(tableOffset + map31Offset); 296 } 297 else 298 {throw new FontFileFormatException("(3,1) nonsymbolic table does NOT exist.");} 299 } 300 301 int format; 302 try 303 {format = fontData.readUnsignedShort();} 304 catch(Exception e) 305 {throw new RuntimeException (e);} 306 switch(format) 308 { 309 case 0: load_cMap_format0(); 311 break; 312 case 4: load_cMap_format4(); 314 break; 315 case 6: load_cMap_format6(); 317 break; 318 default: 319 throw new FontFileFormatException("Format " + format + " cmap table NOT supported."); 320 } 321 } 322 323 328 private void load_cMap_format0( 329 ) 330 { 331 335 OpenTypeFont.this.glyphIndexes = new Hashtable <Integer ,Integer >(256); 336 337 try 338 { 339 fontData.skip(4); 341 for( 344 int code = 0; 345 code < 256; 346 code++ 347 ) 348 { 349 glyphIndexes.put( 350 code, fontData.readUnsignedByte() ); 353 } 354 } 355 catch(Exception e) 356 {throw new RuntimeException (e);} 357 } 358 359 365 private void load_cMap_format4( 366 ) 367 { 368 379 try 380 { 381 int tableLength = fontData.readUnsignedShort(); 385 fontData.skip(2); 387 int segmentCount = fontData.readUnsignedShort() / 2; 389 390 fontData.skip(6); 393 int[] endCodes = new int[segmentCount]; for( 396 int index = 0; 397 index < segmentCount; 398 index++ 399 ) 400 {endCodes[index] = fontData.readUnsignedShort();} 401 402 fontData.skip(2); 404 int[] startCodes = new int[segmentCount]; for( 407 int index = 0; 408 index < segmentCount; 409 index++ 410 ) 411 {startCodes[index] = fontData.readUnsignedShort();} 412 413 short[] deltas = new short[segmentCount]; 415 for( 416 int index = 0; 417 index < segmentCount; 418 index++ 419 ) 420 {deltas[index] = fontData.readShort();} 421 422 int[] rangeOffsets = new int[segmentCount]; for( 425 int index = 0; 426 index < segmentCount; 427 index++ 428 ) 429 {rangeOffsets[index] = fontData.readUnsignedShort();} 430 431 436 int glyphIndexCount = tableLength / 2 - 8 - segmentCount * 4; int[] glyphIds = new int[glyphIndexCount]; for( 441 int index = 0; 442 index < glyphIds.length; 443 index++ 444 ) 445 {glyphIds[index] = fontData.readUnsignedShort();} 446 447 OpenTypeFont.this.glyphIndexes = new Hashtable <Integer ,Integer >(glyphIndexCount); 448 for( 450 int segmentIndex = 0; 451 segmentIndex < segmentCount; 452 segmentIndex++ 453 ) 454 { 455 int endCode = endCodes[segmentIndex]; 456 462 if(endCode != 0xFFFF) 463 {endCode++;} 464 for( 466 int code = startCodes[segmentIndex]; 467 code < endCode; 468 code++ 469 ) 470 { 471 int glyphIndex; 472 if(rangeOffsets[segmentIndex] == 0) { 475 479 glyphIndex = (code + deltas[segmentIndex]) & 0xFFFF; 480 } 481 else { 483 498 int glyphIdIndex = rangeOffsets[segmentIndex] / 2 + (code - startCodes[segmentIndex]) - (segmentCount - segmentIndex); 508 if(glyphIdIndex == 0) {glyphIndex = 0;} 510 else {glyphIndex = (glyphIds[glyphIdIndex] + deltas[segmentIndex]) & 0xFFFF;} 512 } 513 514 glyphIndexes.put( 515 code, glyphIndex ); 518 } 519 } 520 } 521 catch(Exception e) 522 {throw new RuntimeException (e);} 523 } 524 525 529 private void load_cMap_format6( 530 ) 531 { 532 try 533 { 534 fontData.skip(4); 536 int firstCode = fontData.readUnsignedShort(); 537 int codeCount = fontData.readUnsignedShort(); 538 OpenTypeFont.this.glyphIndexes = new Hashtable <Integer ,Integer >(codeCount); 539 for( 540 int code = firstCode, 541 lastCode = firstCode + codeCount; 542 code < lastCode; 543 code++ 544 ) 545 { 546 glyphIndexes.put( 547 code, fontData.readUnsignedShort() ); 550 } 551 } 552 catch(Exception e) 553 {throw new RuntimeException (e);} 554 } 555 556 560 private String load_getName( 561 int id 562 ) 563 throws FontFileFormatException 564 { 565 int tableOffset = tableOffsets.get("name"); 568 if(tableOffset == 0) 569 throw new FontFileFormatException("'name' table does NOT exist."); 570 571 try 572 { 573 fontData.seek(tableOffset + 2); 575 576 int recordCount = fontData.readUnsignedShort(); int storageOffset = fontData.readUnsignedShort(); for( 580 int recordIndex = 0; 581 recordIndex < recordCount; 582 recordIndex++ 583 ) 584 { 585 int platformID = fontData.readUnsignedShort(); if(platformID == PlatformID_Microsoft) 588 { 589 fontData.skip(2); 590 int languageID = fontData.readUnsignedShort(); if(languageID == MicrosoftLanguage_UsEnglish) 593 { 594 int nameID = fontData.readUnsignedShort(); if(nameID == id) 597 { 598 int length = fontData.readUnsignedShort(); int offset = fontData.readUnsignedShort(); 601 fontData.seek(tableOffset + storageOffset + offset); 603 604 return readString(length,platformID); 605 } 606 else 607 {fontData.skip(4);} 608 } 609 else 610 {fontData.skip(6);} 611 } 612 else 613 {fontData.skip(10);} 614 } 615 616 return null; } 618 catch(Exception e) 619 {throw new RuntimeException (e);} 620 } 621 622 625 private void load_glyphKerning( 626 ) 627 throws FontFileFormatException 628 { 629 int tableOffset; 632 try 633 {tableOffset = tableOffsets.get("kern");} 634 catch(Exception e) 635 {return;} 636 637 try 638 { 639 fontData.seek(tableOffset + 2); 641 int subtableCount = fontData.readUnsignedShort(); 643 OpenTypeFont.this.glyphKernings = new Hashtable <Integer ,Integer >(); 644 int subtableOffset = (int)fontData.getPosition(); 645 for( 647 int subtableIndex = 0; 648 subtableIndex < subtableCount; 649 subtableIndex++ 650 ) 651 { 652 fontData.seek(subtableOffset + 2); 654 int length = fontData.readUnsignedShort(); 657 int coverage = fontData.readUnsignedShort(); 664 if((coverage & 0xff00) == 0x0000) 666 { 667 int pairCount = fontData.readUnsignedShort(); 669 fontData.skip(6); 671 for( 673 int pairIndex = 0; 674 pairIndex < pairCount; 675 pairIndex++ 676 ) 677 { 678 int pair = fontData.readInt(); int value = (int)(fontData.readShort() * metrics.unitNorm); 682 683 glyphKernings.put(pair,value); 684 } 685 } 686 687 subtableOffset += length; 688 } 689 } 690 catch(Exception e) 691 {throw new RuntimeException (e);} 692 } 693 694 697 private void load_glyphWidths( 698 ) 699 throws FontFileFormatException 700 { 701 int tableOffset = tableOffsets.get("hmtx"); 704 if(tableOffset == 0) 705 throw new FontFileFormatException("'hmtx' table does NOT exist."); 706 707 try 708 { 709 fontData.seek(tableOffset); 711 OpenTypeFont.this.glyphWidths = new int[metrics.numberOfHMetrics]; for( 713 int index = 0; 714 index < metrics.numberOfHMetrics; 715 index++ 716 ) 717 { 718 glyphWidths[index] = (int)(fontData.readUnsignedShort() * metrics.unitNorm); 720 fontData.skip(2); 722 } 723 } 724 catch(Exception e) 725 {throw new RuntimeException (e);} 726 } 727 728 731 private void load_tables( 732 ) 733 throws FontFileFormatException 734 { 735 int tableOffset = tableOffsets.get("head"); 738 if(tableOffset == 0) 739 throw new FontFileFormatException("'head' table does NOT exist."); 740 741 try 742 { 743 fontData.seek(tableOffset + 16); 745 metrics.flags = fontData.readUnsignedShort(); 746 metrics.unitsPerEm = fontData.readUnsignedShort(); 747 metrics.unitNorm = 1000f / metrics.unitsPerEm; 748 fontData.skip(16); 750 metrics.xMin = fontData.readShort(); 751 metrics.yMin = fontData.readShort(); 752 metrics.xMax = fontData.readShort(); 753 metrics.yMax = fontData.readShort(); 754 metrics.macStyle = fontData.readUnsignedShort(); 755 } 756 catch(Exception e) 757 {throw new RuntimeException (e);} 758 759 tableOffset = tableOffsets.get("OS/2"); 762 if(tableOffset == 0) 763 throw new FontFileFormatException("'OS/2' table does NOT exist."); 764 765 try 766 { 767 fontData.seek(tableOffset); 768 int version = fontData.readUnsignedShort(); 769 fontData.skip(66); 771 metrics.sTypoAscender = fontData.readShort(); 772 metrics.sTypoDescender = fontData.readShort(); 773 metrics.sTypoLineGap = fontData.readShort(); 774 if(version >= 2) 775 { 776 fontData.skip(12); 777 metrics.sxHeight = fontData.readShort(); 778 metrics.sCapHeight = fontData.readShort(); 779 } 780 else 781 { 782 786 metrics.sxHeight = (short)(.5 * metrics.unitsPerEm); 787 metrics.sCapHeight = (short)(.7 * metrics.unitsPerEm); 788 } 789 } 790 catch(Exception e) 791 {throw new RuntimeException (e);} 792 793 tableOffset = tableOffsets.get("hhea"); 796 if(tableOffset == 0) 797 throw new FontFileFormatException("'hhea' table does NOT exist."); 798 799 try 800 { 801 fontData.seek(tableOffset + 4); 803 metrics.ascender = fontData.readShort(); 804 metrics.descender = fontData.readShort(); 805 metrics.lineGap = fontData.readShort(); 806 metrics.advanceWidthMax = fontData.readUnsignedShort(); 807 metrics.minLeftSideBearing = fontData.readShort(); 808 metrics.minRightSideBearing = fontData.readShort(); 809 metrics.xMaxExtent = fontData.readShort(); 810 metrics.caretSlopeRise = fontData.readShort(); 811 metrics.caretSlopeRun = fontData.readShort(); 812 fontData.skip(12); 814 metrics.numberOfHMetrics = fontData.readUnsignedShort(); 815 } 816 catch(Exception e) 817 {throw new RuntimeException (e);} 818 819 tableOffset = tableOffsets.get("post"); 822 if(tableOffset == 0) 823 throw new FontFileFormatException("'post' table does NOT exist."); 824 825 try 826 { 827 fontData.seek(tableOffset + 4); 829 metrics.italicAngle = 830 fontData.readShort() + (float)fontData.readUnsignedShort() / 16384; metrics.underlinePosition = fontData.readShort(); 833 metrics.underlineThickness = fontData.readShort(); 834 metrics.isFixedPitch = (fontData.readInt() != 0); 835 } 836 catch(Exception e) 837 {throw new RuntimeException (e);} 838 } 839 840 843 private String readString( 844 int length, 845 int platformID 846 ) 847 { 848 switch(platformID) 850 { 851 case 0: case PlatformID_Microsoft: 853 return readString_unicode(length); 854 default: 855 return readString_ascii(length); 856 } 857 } 858 859 862 private String readString_ascii( 863 int length 864 ) 865 { 866 870 try 871 { 872 byte[] data = new byte[length]; 873 fontData.read(data); 874 875 return new String (data,Encoding_Latin1); 876 } 877 catch(Exception e) 878 {throw new RuntimeException (e);} 879 } 880 881 884 private String readString_unicode( 885 int length 886 ) 887 { 888 892 try 893 { 894 byte[] data = new byte[length]; 895 fontData.read(data); 896 897 return new String (data,Encoding_Unicode); 898 } 899 catch(Exception e) 900 {throw new RuntimeException (e);} 901 } 902 } 903 905 private FontMetrics metrics; 908 909 private Hashtable <Integer ,Integer > glyphIndexes; 910 private Hashtable <Integer ,Integer > glyphKernings; 911 private int[] glyphWidths; 912 914 public OpenTypeFont( 916 Document context, 917 IInputStream fontData 918 ) 919 { 920 super(context); 921 922 load(fontData); 923 } 924 925 929 public OpenTypeFont( 931 PdfDirectObject baseObject 932 ) 933 {super(baseObject);} 934 936 public Object clone( 939 Document context 940 ) 941 {throw new NotImplementedException();} 942 943 @Override 944 public int getKerning( 945 char textChar1, 946 char textChar2 947 ) 948 { 949 try 950 { 951 return glyphKernings.get( 952 glyphIndexes.get((int)textChar1) << 16 + glyphIndexes.get((int)textChar2) ); 955 } 956 catch(Exception e) 957 {return 0;} 958 } 959 960 @Override 961 public int getKerning( 962 String text 963 ) 964 { 965 int kerning = 0; 966 if(glyphKernings != null) 968 { 969 char textChars[] = text.toCharArray(); 970 for( 971 int index = 0, 972 length = text.length() - 1; 973 index < length; 974 index++ 975 ) 976 { 977 kerning += getKerning( 978 textChars[index], 979 textChars[index + 1] 980 ); 981 } 982 } 983 984 return kerning; 985 } 986 987 @Override 988 public int getWidth( 989 char textChar 990 ) 991 {return glyphWidths[glyphIndexes.get((int)textChar)];} 992 993 @Override 994 public int getWidth( 995 String text 996 ) 997 { 998 int width = 0; 999 char textChars[] = text.toCharArray(); 1000 for( 1001 int index = 0, 1002 length = text.length(); 1003 index < length; 1004 index++ 1005 ) 1006 {width += getWidth(textChars[index]);} 1007 1008 return width; 1009 } 1010 1012 1015 private void load( 1016 IInputStream fontData 1017 ) 1018 { 1019 try 1020 { 1021 Parser parser = new Parser(fontData); 1022 1023 getBaseDataObject().put( 1025 PdfName.Subtype, 1026 parser.subType 1027 ); 1028 getBaseDataObject().put( 1030 PdfName.BaseFont, 1031 new PdfName(parser.fontName) 1032 ); 1033 1043 if(metrics.isCustomEncoding) { 1046 1050 } 1051 else { 1053 getBaseDataObject().put(PdfName.Encoding,PdfName.WinAnsiEncoding); 1054 } 1055 int firstChar = 0; 1057 int lastChar = 255; 1058 getBaseDataObject().put( 1060 PdfName.FirstChar, 1061 new PdfInteger(firstChar) 1062 ); 1063 getBaseDataObject().put( 1065 PdfName.LastChar, 1066 new PdfInteger(lastChar) 1067 ); 1068 PdfArray widthsObject = new PdfArray(lastChar - firstChar + 1); 1070 for( 1071 int code = firstChar; 1072 code <= lastChar; 1073 code++ 1074 ) 1075 { 1076 int width; 1077 try 1078 {width = glyphWidths[glyphIndexes.get(code)];} 1079 catch(Exception e) 1080 {width = 0;} 1081 1082 widthsObject.add(new PdfInteger(width)); 1083 } 1084 getBaseDataObject().put( 1085 PdfName.Widths, 1086 widthsObject 1087 ); 1088 getBaseDataObject().put( 1090 PdfName.FontDescriptor, 1091 load_createFontDescriptor(fontData).getReference() 1092 ); 1093 } 1094 catch(Exception e) 1095 {throw new RuntimeException (e);} 1096 } 1097 1098 1101 private PdfIndirectObject load_createFontDescriptor( 1102 IInputStream fontData 1103 ) 1104 { 1105 PdfDictionary fontDescriptor = new PdfDictionary(); 1106 fontDescriptor.put( 1108 PdfName.Type, 1109 PdfName.FontDescriptor 1110 ); 1111 fontDescriptor.put( 1113 PdfName.FontName, 1114 getBaseDataObject().get(PdfName.BaseFont) 1115 ); 1116 int flags = 0; 1118 if(metrics.isFixedPitch) 1119 {flags |= FlagsEnum.FixedPitch.getCode();} 1120 if(metrics.isCustomEncoding) 1121 {flags |= FlagsEnum.Symbolic.getCode();} 1122 else 1123 {flags |= FlagsEnum.Nonsymbolic.getCode();} 1124 fontDescriptor.put( 1125 PdfName.Flags, 1126 new PdfInteger(flags) 1127 ); 1128 fontDescriptor.put( 1130 PdfName.FontBBox, 1131 new PdfRectangle( 1132 metrics.xMin * metrics.unitNorm, 1133 metrics.yMin * metrics.unitNorm, 1134 metrics.xMax * metrics.unitNorm, 1135 metrics.yMax * metrics.unitNorm 1136 ) 1137 ); 1138 fontDescriptor.put( 1140 PdfName.ItalicAngle, 1141 new PdfReal(metrics.italicAngle) 1142 ); 1143 if(metrics.ascender == 0) 1145 { 1146 fontDescriptor.put( 1147 PdfName.Ascent, 1148 new PdfReal(metrics.sTypoAscender * metrics.unitNorm) 1149 ); 1150 } 1151 else 1152 { 1153 fontDescriptor.put( 1154 PdfName.Ascent, 1155 new PdfReal(metrics.ascender * metrics.unitNorm) 1156 ); 1157 } 1158 if(metrics.descender == 0) 1160 { 1161 fontDescriptor.put( 1162 PdfName.Descent, 1163 new PdfReal(metrics.sTypoDescender * metrics.unitNorm) 1164 ); 1165 } 1166 else 1167 { 1168 fontDescriptor.put( 1169 PdfName.Descent, 1170 new PdfReal(metrics.descender * metrics.unitNorm) 1171 ); 1172 } 1173 fontDescriptor.put( 1175 PdfName.Leading, 1176 new PdfReal(metrics.sTypoLineGap * metrics.unitNorm) 1177 ); 1178 fontDescriptor.put( 1180 PdfName.CapHeight, 1181 new PdfReal(metrics.sCapHeight * metrics.unitNorm) 1182 ); 1183 1189 fontDescriptor.put( 1190 PdfName.StemV, 1191 new PdfInteger(100) 1192 ); 1193 PdfIndirectObject fontFile = getFile().getIndirectObjects().add( 1196 new PdfStream( 1197 new PdfDictionary( 1198 new PdfName[]{PdfName.Subtype}, 1199 new PdfDirectObject[]{PdfName.OpenType} 1200 ), 1201 new Buffer(fontData.toByteArray()) 1202 ) 1203 ); 1204 fontDescriptor.put( 1205 PdfName.FontFile3, 1206 fontFile.getReference() 1207 ); 1208 1209 return getFile().getIndirectObjects().add(fontDescriptor); 1210 } 1211 } | Popular Tags |