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.cff; 18 19 import java.io.IOException; 20 import java.util.ArrayList; 21 import java.util.Arrays; 22 import java.util.Collections; 23 import java.util.List; 24 25 import org.apache.fontbox.cff.charset.CFFCharset; 26 import org.apache.fontbox.cff.charset.CFFExpertCharset; 27 import org.apache.fontbox.cff.charset.CFFExpertSubsetCharset; 28 import org.apache.fontbox.cff.charset.CFFISOAdobeCharset; 29 import org.apache.fontbox.cff.encoding.CFFEncoding; 30 import org.apache.fontbox.cff.encoding.CFFExpertEncoding; 31 import org.apache.fontbox.cff.encoding.CFFStandardEncoding; 32 33 /** 34 * This class represents a parser for a CFF font. 35 * @author Villu Ruusmann 36 * @version $Revision: 1.0 $ 37 */ 38 public class CFFParser 39 { 40 41 private CFFDataInput input = null; 42 private Header header = null; 43 private IndexData nameIndex = null; 44 private IndexData topDictIndex = null; 45 private IndexData stringIndex = null; 46 private IndexData globalSubrIndex = null; 47 48 /** 49 * Parsing CFF Font using a byte array as input. 50 * @param bytes the given byte array 51 * @return the parsed CFF fonts 52 * @throws IOException If there is an error reading from the stream 53 */ 54 public List<CFFFont> parse(byte[] bytes) throws IOException 55 { 56 input = new CFFDataInput(bytes); 57 header = readHeader(input); 58 nameIndex = readIndexData(input); 59 topDictIndex = readIndexData(input); 60 stringIndex = readIndexData(input); 61 globalSubrIndex = readIndexData(input); 62 63 List<CFFFont> fonts = new ArrayList<CFFFont>(); 64 for (int i = 0; i < nameIndex.count; i++) 65 { 66 CFFFont font = parseFont(i); 67 fonts.add(font); 68 } 69 return fonts; 70 } 71 72 private static Header readHeader(CFFDataInput input) throws IOException 73 { 74 Header header = new Header(); 75 header.major = input.readCard8(); 76 header.minor = input.readCard8(); 77 header.hdrSize = input.readCard8(); 78 header.offSize = input.readOffSize(); 79 return header; 80 } 81 82 private static IndexData readIndexData(CFFDataInput input) 83 throws IOException 84 { 85 IndexData index = new IndexData(); 86 index.count = input.readCard16(); 87 if (index.count == 0) 88 { 89 return index; 90 } 91 index.offSize = input.readOffSize(); 92 index.offset = new int[index.count + 1]; 93 for (int i = 0; i < index.offset.length; i++) 94 { 95 index.offset[i] = input.readOffset(index.offSize); 96 } 97 index.data = new int[index.offset[index.offset.length - 1] 98 - index.offset[0]]; 99 for (int i = 0; i < index.data.length; i++) 100 { 101 index.data[i] = input.readCard8(); 102 } 103 return index; 104 } 105 106 private static DictData readDictData(CFFDataInput input) throws IOException 107 { 108 DictData dict = new DictData(); 109 dict.entries = new ArrayList<DictData.Entry>(); 110 while (input.hasRemaining()) 111 { 112 DictData.Entry entry = readEntry(input); 113 dict.entries.add(entry); 114 } 115 return dict; 116 } 117 118 private static DictData.Entry readEntry(CFFDataInput input) 119 throws IOException 120 { 121 DictData.Entry entry = new DictData.Entry(); 122 while (true) 123 { 124 int b0 = input.readUnsignedByte(); 125 126 if (b0 >= 0 && b0 <= 21) 127 { 128 entry.operator = readOperator(input, b0); 129 break; 130 } 131 else if (b0 == 28 || b0 == 29) 132 { 133 entry.operands.add(readIntegerNumber(input, b0)); 134 } 135 else if (b0 == 30) 136 { 137 entry.operands.add(readRealNumber(input, b0)); 138 } 139 else if (b0 >= 32 && b0 <= 254) 140 { 141 entry.operands.add(readIntegerNumber(input, b0)); 142 } 143 else 144 { 145 throw new IllegalArgumentException(); 146 } 147 } 148 return entry; 149 } 150 151 private static CFFOperator readOperator(CFFDataInput input, int b0) 152 throws IOException 153 { 154 CFFOperator.Key key = readOperatorKey(input, b0); 155 return CFFOperator.getOperator(key); 156 } 157 158 private static CFFOperator.Key readOperatorKey(CFFDataInput input, int b0) 159 throws IOException 160 { 161 if (b0 == 12) 162 { 163 int b1 = input.readUnsignedByte(); 164 return new CFFOperator.Key(b0, b1); 165 } 166 return new CFFOperator.Key(b0); 167 } 168 169 private static Integer readIntegerNumber(CFFDataInput input, int b0) 170 throws IOException 171 { 172 if (b0 == 28) 173 { 174 int b1 = input.readUnsignedByte(); 175 int b2 = input.readUnsignedByte(); 176 return Integer.valueOf((short) (b1 << 8 | b2)); 177 } 178 else if (b0 == 29) 179 { 180 int b1 = input.readUnsignedByte(); 181 int b2 = input.readUnsignedByte(); 182 int b3 = input.readUnsignedByte(); 183 int b4 = input.readUnsignedByte(); 184 return Integer.valueOf(b1 << 24 | b2 << 16 | b3 << 8 | b4); 185 } 186 else if (b0 >= 32 && b0 <= 246) 187 { 188 return Integer.valueOf(b0 - 139); 189 } 190 else if (b0 >= 247 && b0 <= 250) 191 { 192 int b1 = input.readUnsignedByte(); 193 return Integer.valueOf((b0 - 247) * 256 + b1 + 108); 194 } 195 else if (b0 >= 251 && b0 <= 254) 196 { 197 int b1 = input.readUnsignedByte(); 198 return Integer.valueOf(-(b0 - 251) * 256 - b1 - 108); 199 } 200 else 201 { 202 throw new IllegalArgumentException(); 203 } 204 } 205 206 private static Double readRealNumber(CFFDataInput input, int b0) 207 throws IOException 208 { 209 StringBuffer sb = new StringBuffer(); 210 boolean done = false; 211 while (!done) 212 { 213 int b = input.readUnsignedByte(); 214 int[] nibbles = { b / 16, b % 16 }; 215 for (int nibble : nibbles) 216 { 217 switch (nibble) 218 { 219 case 0x0: 220 case 0x1: 221 case 0x2: 222 case 0x3: 223 case 0x4: 224 case 0x5: 225 case 0x6: 226 case 0x7: 227 case 0x8: 228 case 0x9: 229 sb.append(nibble); 230 break; 231 case 0xa: 232 sb.append("."); 233 break; 234 case 0xb: 235 sb.append("E"); 236 break; 237 case 0xc: 238 sb.append("E-"); 239 break; 240 case 0xd: 241 break; 242 case 0xe: 243 sb.append("-"); 244 break; 245 case 0xf: 246 done = true; 247 break; 248 default: 249 throw new IllegalArgumentException(); 250 } 251 } 252 } 253 return Double.valueOf(sb.toString()); 254 } 255 256 private CFFFont parseFont(int index) throws IOException 257 { 258 CFFFont font = new CFFFont(); 259 DataInput nameInput = new DataInput(nameIndex.getBytes(index)); 260 String name = nameInput.getString(); 261 font.setName(name); 262 CFFDataInput topDictInput = new CFFDataInput(topDictIndex 263 .getBytes(index)); 264 DictData topDict = readDictData(topDictInput); 265 DictData.Entry syntheticBaseEntry = topDict.getEntry("SyntheticBase"); 266 if (syntheticBaseEntry != null) 267 { 268 throw new IOException("Synthetic Fonts are not supported"); 269 } 270 DictData.Entry rosEntry = topDict.getEntry("ROS"); 271 if (rosEntry != null) 272 { 273 throw new IOException("CID-keyed Fonts are not supported"); 274 } 275 font.addValueToTopDict("version", getString(topDict,"version")); 276 font.addValueToTopDict("Notice", getString(topDict,"Notice")); 277 font.addValueToTopDict("Copyright", getString(topDict,"Copyright")); 278 font.addValueToTopDict("FullName", getString(topDict,"FullName")); 279 font.addValueToTopDict("FamilyName", getString(topDict,"FamilyName")); 280 font.addValueToTopDict("Weight", getString(topDict,"Weight")); 281 font.addValueToTopDict("isFixedPitch", getBoolean(topDict, "isFixedPitch", false)); 282 font.addValueToTopDict("ItalicAngle", getNumber(topDict, "ItalicAngle", 0)); 283 font.addValueToTopDict("UnderlinePosition", getNumber(topDict, "UnderlinePosition", -100)); 284 font.addValueToTopDict("UnderlineThickness", getNumber(topDict, "UnderlineThickness", 50)); 285 font.addValueToTopDict("PaintType", getNumber(topDict, "PaintType", 0)); 286 font.addValueToTopDict("CharstringType", getNumber(topDict, "CharstringType", 2)); 287 font.addValueToTopDict("FontMatrix", getArray(topDict, "FontMatrix", Arrays 288 .<Number> asList(Double.valueOf(0.001), Double.valueOf(0), 289 Double.valueOf(0), Double.valueOf(0.001), Double 290 .valueOf(0), Double.valueOf(0)))); 291 font.addValueToTopDict("UniqueID", getNumber(topDict, "UniqueID", null)); 292 font.addValueToTopDict("FontBBox", getArray(topDict, "FontBBox", Arrays 293 .<Number> asList(Integer.valueOf(0), Integer.valueOf(0), 294 Integer.valueOf(0), Integer.valueOf(0)))); 295 font.addValueToTopDict("StrokeWidth", getNumber(topDict, "StrokeWidth", 0)); 296 font.addValueToTopDict("XUID", getArray(topDict, "XUID", null)); 297 DictData.Entry charStringsEntry = topDict.getEntry("CharStrings"); 298 int charStringsOffset = charStringsEntry.getNumber(0).intValue(); 299 input.setPosition(charStringsOffset); 300 IndexData charStringsIndex = readIndexData(input); 301 DictData.Entry charsetEntry = topDict.getEntry("charset"); 302 CFFCharset charset; 303 int charsetId = charsetEntry != null ? charsetEntry.getNumber(0) 304 .intValue() : 0; 305 if (charsetId == 0) 306 { 307 charset = CFFISOAdobeCharset.getInstance(); 308 } 309 else if (charsetId == 1) 310 { 311 charset = CFFExpertCharset.getInstance(); 312 } 313 else if (charsetId == 2) 314 { 315 charset = CFFExpertSubsetCharset.getInstance(); 316 } 317 else 318 { 319 input.setPosition(charsetId); 320 charset = readCharset(input, charStringsIndex.count); 321 } 322 font.setCharset(charset); 323 font.getCharStringsDict().put(".notdef", charStringsIndex.getBytes(0)); 324 int[] gids = new int[charStringsIndex.count]; 325 List<CFFCharset.Entry> glyphEntries = charset.getEntries(); 326 for (int i = 1; i < charStringsIndex.count; i++) 327 { 328 CFFCharset.Entry glyphEntry = glyphEntries.get(i - 1); 329 gids[i - 1] = glyphEntry.getSID(); 330 font.getCharStringsDict().put(glyphEntry.getName(), charStringsIndex.getBytes(i)); 331 } 332 DictData.Entry encodingEntry = topDict.getEntry("Encoding"); 333 CFFEncoding encoding; 334 int encodingId = encodingEntry != null ? encodingEntry.getNumber(0) 335 .intValue() : 0; 336 if (encodingId == 0) 337 { 338 encoding = CFFStandardEncoding.getInstance(); 339 } 340 else if (encodingId == 1) 341 { 342 encoding = CFFExpertEncoding.getInstance(); 343 } 344 else 345 { 346 input.setPosition(encodingId); 347 encoding = readEncoding(input, gids); 348 } 349 font.setEncoding(encoding); 350 DictData.Entry privateEntry = topDict.getEntry("Private"); 351 int privateOffset = privateEntry.getNumber(1).intValue(); 352 input.setPosition(privateOffset); 353 int privateSize = privateEntry.getNumber(0).intValue(); 354 CFFDataInput privateDictData = new CFFDataInput(input.readBytes(privateSize)); 355 DictData privateDict = readDictData(privateDictData); 356 font.addValueToPrivateDict("BlueValues", getDelta(privateDict, "BlueValues", null)); 357 font.addValueToPrivateDict("OtherBlues", getDelta(privateDict, "OtherBlues", null)); 358 font.addValueToPrivateDict("FamilyBlues", getDelta(privateDict, "FamilyBlues", null)); 359 font.addValueToPrivateDict("FamilyOtherBlues", getDelta(privateDict, "FamilyOtherBlues", null)); 360 font.addValueToPrivateDict("BlueScale", getNumber(privateDict, "BlueScale", Double.valueOf(0.039625))); 361 font.addValueToPrivateDict("BlueShift", getNumber(privateDict, "BlueShift", Integer.valueOf(7))); 362 font.addValueToPrivateDict("BlueFuzz", getNumber(privateDict, "BlueFuzz", Integer.valueOf(1))); 363 font.addValueToPrivateDict("StdHW", getNumber(privateDict, "StdHW", null)); 364 font.addValueToPrivateDict("StdVW", getNumber(privateDict, "StdVW", null)); 365 font.addValueToPrivateDict("StemSnapH", getDelta(privateDict, "StemSnapH", null)); 366 font.addValueToPrivateDict("StemSnapV", getDelta(privateDict, "StemSnapV", null)); 367 font.addValueToPrivateDict("ForceBold", getBoolean(privateDict, "ForceBold", false)); 368 font.addValueToPrivateDict("LanguageGroup", getNumber(privateDict, "LanguageGroup", Integer.valueOf(0))); 369 font.addValueToPrivateDict("ExpansionFactor", getNumber(privateDict, "ExpansionFactor", Double.valueOf(0.06))); 370 font.addValueToPrivateDict("initialRandomSeed", getNumber(privateDict, "initialRandomSeed", Integer.valueOf(0))); 371 font.addValueToPrivateDict("defaultWidthX", getNumber(privateDict, "defaultWidthX", Integer.valueOf(0))); 372 font.addValueToPrivateDict("nominalWidthX", getNumber(privateDict, "nominalWidthX", Integer.valueOf(0))); 373 return font; 374 } 375 376 private String readString(int index) throws IOException 377 { 378 if (index >= 0 && index <= 390) 379 { 380 return CFFStandardString.getName(index); 381 } 382 DataInput dataInput = new DataInput(stringIndex.getBytes(index - 391)); 383 return dataInput.getString(); 384 } 385 386 private String getString(DictData dict, String name) throws IOException 387 { 388 DictData.Entry entry = dict.getEntry(name); 389 return (entry != null ? readString(entry.getNumber(0).intValue()) : null); 390 } 391 392 private Boolean getBoolean(DictData dict, String name, boolean defaultValue) throws IOException 393 { 394 DictData.Entry entry = dict.getEntry(name); 395 return entry != null ? entry.getBoolean(0) : defaultValue; 396 } 397 398 private Number getNumber(DictData dict, String name, Number defaultValue) throws IOException 399 { 400 DictData.Entry entry = dict.getEntry(name); 401 return entry != null ? entry.getNumber(0) : defaultValue; 402 } 403 404 // TODO Where is the difference to getDelta?? 405 private List<Number> getArray(DictData dict, String name, List<Number> defaultValue) throws IOException 406 { 407 DictData.Entry entry = dict.getEntry(name); 408 return entry != null ? entry.getArray() : defaultValue; 409 } 410 411 // TODO Where is the difference to getArray?? 412 private List<Number> getDelta(DictData dict, String name, List<Number> defaultValue) throws IOException 413 { 414 DictData.Entry entry = dict.getEntry(name); 415 return entry != null ? entry.getArray() : defaultValue; 416 } 417 418 private CFFEncoding readEncoding(CFFDataInput dataInput, int[] gids) 419 throws IOException 420 { 421 int format = dataInput.readCard8(); 422 int baseFormat = format & 0x7f; 423 424 if (baseFormat == 0) 425 { 426 return readFormat0Encoding(dataInput, format, gids); 427 } 428 else if (baseFormat == 1) 429 { 430 return readFormat1Encoding(dataInput, format, gids); 431 } 432 else 433 { 434 throw new IllegalArgumentException(); 435 } 436 } 437 438 private Format0Encoding readFormat0Encoding(CFFDataInput dataInput, int format, 439 int[] gids) throws IOException 440 { 441 Format0Encoding encoding = new Format0Encoding(); 442 encoding.format = format; 443 encoding.nCodes = dataInput.readCard8(); 444 encoding.code = new int[encoding.nCodes]; 445 for (int i = 0; i < encoding.code.length; i++) 446 { 447 encoding.code[i] = dataInput.readCard8(); 448 encoding.register(encoding.code[i], gids[i]); 449 } 450 if ((format & 0x80) != 0) 451 { 452 readSupplement(dataInput, encoding); 453 } 454 return encoding; 455 } 456 457 private Format1Encoding readFormat1Encoding(CFFDataInput dataInput, int format, 458 int[] gids) throws IOException 459 { 460 Format1Encoding encoding = new Format1Encoding(); 461 encoding.format = format; 462 encoding.nRanges = dataInput.readCard8(); 463 int count = 0; 464 encoding.range = new Format1Encoding.Range1[encoding.nRanges]; 465 for (int i = 0; i < encoding.range.length; i++) 466 { 467 Format1Encoding.Range1 range = new Format1Encoding.Range1(); 468 range.first = dataInput.readCard8(); 469 range.nLeft = dataInput.readCard8(); 470 encoding.range[i] = range; 471 for (int j = 0; j < 1 + range.nLeft; j++) 472 { 473 encoding.register(range.first + j, gids[count + j]); 474 } 475 count += 1 + range.nLeft; 476 } 477 if ((format & 0x80) != 0) 478 { 479 readSupplement(dataInput, encoding); 480 } 481 return encoding; 482 } 483 484 private void readSupplement(CFFDataInput dataInput, EmbeddedEncoding encoding) 485 throws IOException 486 { 487 encoding.nSups = dataInput.readCard8(); 488 encoding.supplement = new EmbeddedEncoding.Supplement[encoding.nSups]; 489 for (int i = 0; i < encoding.supplement.length; i++) 490 { 491 EmbeddedEncoding.Supplement supplement = new EmbeddedEncoding.Supplement(); 492 supplement.code = dataInput.readCard8(); 493 supplement.glyph = dataInput.readSID(); 494 encoding.supplement[i] = supplement; 495 } 496 } 497 498 private CFFCharset readCharset(CFFDataInput dataInput, int nGlyphs) 499 throws IOException 500 { 501 int format = dataInput.readCard8(); 502 if (format == 0) 503 { 504 return readFormat0Charset(dataInput, format, nGlyphs); 505 } 506 else if (format == 1) 507 { 508 return readFormat1Charset(dataInput, format, nGlyphs); 509 } 510 else 511 { 512 throw new IllegalArgumentException(); 513 } 514 } 515 516 private Format0Charset readFormat0Charset(CFFDataInput dataInput, int format, 517 int nGlyphs) throws IOException 518 { 519 Format0Charset charset = new Format0Charset(); 520 charset.format = format; 521 charset.glyph = new int[nGlyphs - 1]; 522 for (int i = 0; i < charset.glyph.length; i++) 523 { 524 charset.glyph[i] = dataInput.readSID(); 525 charset.register(charset.glyph[i], readString(charset.glyph[i])); 526 } 527 return charset; 528 } 529 530 private Format1Charset readFormat1Charset(CFFDataInput dataInput, int format, 531 int nGlyphs) throws IOException 532 { 533 Format1Charset charset = new Format1Charset(); 534 charset.format = format; 535 charset.range = new Format1Charset.Range1[0]; 536 for (int i = 0; i < nGlyphs - 1;) 537 { 538 Format1Charset.Range1[] newRange = new Format1Charset.Range1[charset.range.length + 1]; 539 System.arraycopy(charset.range, 0, newRange, 0, 540 charset.range.length); 541 charset.range = newRange; 542 Format1Charset.Range1 range = new Format1Charset.Range1(); 543 range.first = dataInput.readSID(); 544 range.nLeft = dataInput.readCard8(); 545 charset.range[charset.range.length - 1] = range; 546 for (int j = 0; j < 1 + range.nLeft; j++) 547 { 548 charset.register(range.first + j, readString(range.first + j)); 549 } 550 i += 1 + range.nLeft; 551 } 552 return charset; 553 } 554 555 /** 556 * Inner class holding the header of a CFF font. 557 */ 558 private static class Header 559 { 560 private int major; 561 private int minor; 562 private int hdrSize; 563 private int offSize; 564 565 @Override 566 public String toString() 567 { 568 return getClass().getName() + "[major=" + major + ", minor=" 569 + minor + ", hdrSize=" + hdrSize + ", offSize=" + offSize 570 + "]"; 571 } 572 } 573 574 /** 575 * Inner class holding the IndexData of a CFF font. 576 */ 577 private static class IndexData 578 { 579 private int count; 580 private int offSize; 581 private int[] offset; 582 private int[] data; 583 584 public byte[] getBytes(int index) 585 { 586 int length = offset[index + 1] - offset[index]; 587 byte[] bytes = new byte[length]; 588 for (int i = 0; i < length; i++) 589 { 590 bytes[i] = (byte) data[offset[index] - 1 + i]; 591 } 592 return bytes; 593 } 594 595 @Override 596 public String toString() 597 { 598 return getClass().getName() + "[count=" + count + ", offSize=" 599 + offSize + ", offset=" + Arrays.toString(offset) 600 + ", data=" + Arrays.toString(data) + "]"; 601 } 602 } 603 604 /** 605 * Inner class holding the DictData of a CFF font. 606 */ 607 private static class DictData 608 { 609 610 private List<Entry> entries = null; 611 612 public Entry getEntry(CFFOperator.Key key) 613 { 614 return getEntry(CFFOperator.getOperator(key)); 615 } 616 617 public Entry getEntry(String name) 618 { 619 return getEntry(CFFOperator.getOperator(name)); 620 } 621 622 private Entry getEntry(CFFOperator operator) 623 { 624 for (Entry entry : entries) 625 { 626 if (entry.operator.equals(operator)) 627 { 628 return entry; 629 } 630 } 631 return null; 632 } 633 634 /** 635 * {@inheritDoc} 636 */ 637 public String toString() 638 { 639 return getClass().getName() + "[entries=" + entries + "]"; 640 } 641 642 /** 643 * Inner class holding an operand of a CFF font. 644 */ 645 private static class Entry 646 { 647 private List<Number> operands = new ArrayList<Number>(); 648 private CFFOperator operator = null; 649 650 public Number getNumber(int index) 651 { 652 return operands.get(index); 653 } 654 655 public Boolean getBoolean(int index) 656 { 657 Number operand = operands.get(index); 658 if (operand instanceof Integer) 659 { 660 switch (operand.intValue()) 661 { 662 case 0: 663 return Boolean.FALSE; 664 case 1: 665 return Boolean.TRUE; 666 default: 667 break; 668 } 669 } 670 throw new IllegalArgumentException(); 671 } 672 673 // TODO unused?? 674 public Integer getSID(int index) 675 { 676 Number operand = operands.get(index); 677 if (operand instanceof Integer) 678 { 679 return (Integer) operand; 680 } 681 throw new IllegalArgumentException(); 682 } 683 684 // TODO Where is the difference to getDelta?? 685 public List<Number> getArray() 686 { 687 return operands; 688 } 689 690 // TODO Where is the difference to getArray?? 691 public List<Number> getDelta() 692 { 693 return operands; 694 } 695 696 @Override 697 public String toString() 698 { 699 return getClass().getName() + "[operands=" + operands 700 + ", operator=" + operator + "]"; 701 } 702 } 703 } 704 705 /** 706 * Inner class representing an embedded CFF encoding. 707 */ 708 abstract static class EmbeddedEncoding extends CFFEncoding 709 { 710 711 private int nSups; 712 private Supplement[] supplement; 713 714 @Override 715 public boolean isFontSpecific() 716 { 717 return true; 718 } 719 720 List<Supplement> getSupplements() 721 { 722 if(supplement == null){ 723 return Collections.<Supplement>emptyList(); 724 } 725 return Arrays.asList(supplement); 726 } 727 728 /** 729 * Inner class representing a supplement for an encoding. 730 */ 731 static class Supplement 732 { 733 private int code; 734 private int glyph; 735 736 int getCode(){ 737 return code; 738 } 739 740 int getGlyph(){ 741 return glyph; 742 } 743 744 @Override 745 public String toString() 746 { 747 return getClass().getName() + "[code=" + code + ", glyph=" 748 + glyph + "]"; 749 } 750 } 751 } 752 753 /** 754 * Inner class representing a Format0 encoding. 755 */ 756 private static class Format0Encoding extends EmbeddedEncoding 757 { 758 private int format; 759 private int nCodes; 760 private int[] code; 761 762 @Override 763 public String toString() 764 { 765 return getClass().getName() + "[format=" + format + ", nCodes=" 766 + nCodes + ", code=" + Arrays.toString(code) 767 + ", supplement=" + Arrays.toString(super.supplement) + "]"; 768 } 769 } 770 771 /** 772 * Inner class representing a Format1 encoding. 773 */ 774 private static class Format1Encoding extends EmbeddedEncoding 775 { 776 private int format; 777 private int nRanges; 778 private Range1[] range; 779 780 @Override 781 public String toString() 782 { 783 return getClass().getName() + "[format=" + format + ", nRanges=" 784 + nRanges + ", range=" + Arrays.toString(range) 785 + ", supplement=" + Arrays.toString(super.supplement) + "]"; 786 } 787 788 /** 789 * Inner class representing a range of an encoding. 790 */ 791 private static class Range1 792 { 793 private int first; 794 private int nLeft; 795 796 @Override 797 public String toString() 798 { 799 return getClass().getName() + "[first=" + first + ", nLeft=" 800 + nLeft + "]"; 801 } 802 } 803 } 804 805 /** 806 * Inner class representing an embedded CFF charset. 807 */ 808 abstract static class EmbeddedCharset extends CFFCharset 809 { 810 @Override 811 public boolean isFontSpecific() 812 { 813 return true; 814 } 815 } 816 817 /** 818 * Inner class representing a Format0 charset. 819 */ 820 private static class Format0Charset extends EmbeddedCharset 821 { 822 private int format; 823 private int[] glyph; 824 825 @Override 826 public String toString() 827 { 828 return getClass().getName() + "[format=" + format + ", glyph=" 829 + Arrays.toString(glyph) + "]"; 830 } 831 } 832 833 /** 834 * Inner class representing a Format1 charset. 835 */ 836 private static class Format1Charset extends EmbeddedCharset 837 { 838 private int format; 839 private Range1[] range; 840 841 @Override 842 public String toString() 843 { 844 return getClass().getName() + "[format=" + format + ", range=" 845 + Arrays.toString(range) + "]"; 846 } 847 848 /** 849 * Inner class representing a range of a charset. 850 */ 851 private static class Range1 852 { 853 private int first; 854 private int nLeft; 855 856 @Override 857 public String toString() 858 { 859 return getClass().getName() + "[first=" + first + ", nLeft=" 860 + nLeft + "]"; 861 } 862 } 863 } 864 }