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 18 /* $Id$ */ 19 20 package org.apache.fop.fonts.cff; 21 22 import java.io.IOException; 23 import java.util.ArrayList; 24 import java.util.LinkedHashMap; 25 import java.util.List; 26 import java.util.Map; 27 28 import org.apache.fontbox.cff.CFFDataInput; 29 import org.apache.fontbox.cff.CFFOperator; 30 31 import org.apache.fop.fonts.truetype.FontFileReader; 32 import org.apache.fop.fonts.truetype.OTFFile; 33 34 /** 35 * A class to read the CFF data from an OTF CFF font file. 36 */ 37 public class CFFDataReader { 38 private CFFDataInput cffData; 39 40 private byte[] header; 41 private CFFIndexData nameIndex; 42 private CFFIndexData topDICTIndex; 43 private CFFIndexData stringIndex; 44 private CFFIndexData charStringIndex; 45 private CFFIndexData globalIndexSubr; 46 private CFFIndexData localIndexSubr; 47 private CustomEncoding encoding; 48 private FDSelect fdSelect; 49 private List<FontDict> fdFonts; 50 51 private static final int DOUBLE_BYTE_OPERATOR = 12; 52 private static final int NUM_STANDARD_STRINGS = 391; 53 54 /** Commonly used parsed dictionaries */ 55 private LinkedHashMap<String, DICTEntry> topDict; 56 CFFDataReader()57 public CFFDataReader() { 58 59 } 60 61 /** 62 * Constructor for the CFF data reader which accepts the CFF byte data 63 * as an argument. 64 * @param cffDataArray A byte array which holds the CFF data 65 */ CFFDataReader(byte[] cffDataArray)66 public CFFDataReader(byte[] cffDataArray) throws IOException { 67 cffData = new CFFDataInput(cffDataArray); 68 readCFFData(); 69 } 70 71 /** 72 * Constructor for the CFF data reader which accepts a FontFileReader object 73 * which points to the original font file as an argument. 74 * @param fontFile The font file as represented by a FontFileReader object 75 */ CFFDataReader(FontFileReader fontFile)76 public CFFDataReader(FontFileReader fontFile) throws IOException { 77 cffData = new CFFDataInput(OTFFile.getCFFData(fontFile)); 78 readCFFData(); 79 } 80 readCFFData()81 private void readCFFData() throws IOException { 82 header = readHeader(); 83 nameIndex = readIndex(); 84 topDICTIndex = readIndex(); 85 topDict = parseDictData(topDICTIndex.getData()); 86 stringIndex = readIndex(); 87 globalIndexSubr = readIndex(); 88 charStringIndex = readCharStringIndex(); 89 encoding = readEncoding(); 90 fdSelect = readFDSelect(); 91 localIndexSubr = readLocalIndexSubrs(); 92 fdFonts = parseCIDData(); 93 } 94 getPrivateDict(DICTEntry privateEntry)95 public Map<String, DICTEntry> getPrivateDict(DICTEntry privateEntry) throws IOException { 96 return parseDictData(getPrivateDictBytes(privateEntry)); 97 } 98 getPrivateDictBytes(DICTEntry privateEntry)99 public byte[] getPrivateDictBytes(DICTEntry privateEntry) throws IOException { 100 int privateLength = privateEntry.getOperands().get(0).intValue(); 101 int privateOffset = privateEntry.getOperands().get(1).intValue(); 102 return getCFFOffsetBytes(privateOffset, privateLength); 103 } 104 105 /** 106 * Retrieves a number of bytes from the CFF data stream 107 * @param offset The offset of the bytes to retrieve 108 * @param length The number of bytes to retrieve 109 * @return Returns a byte array of requested bytes 110 * @throws IOException Throws an IO Exception if an error occurs 111 */ getCFFOffsetBytes(int offset, int length)112 private byte[] getCFFOffsetBytes(int offset, int length) throws IOException { 113 cffData.setPosition(offset); 114 return cffData.readBytes(length); 115 } 116 117 /** 118 * Parses the dictionary data and returns a map of objects for each entry 119 * @param dictData The data for the dictionary data 120 * @return Returns a map of type DICTEntry identified by the operand name 121 * @throws IOException Throws an IO Exception if an error occurs 122 */ parseDictData(byte[] dictData)123 public LinkedHashMap<String, DICTEntry> parseDictData(byte[] dictData) throws IOException { 124 LinkedHashMap<String, DICTEntry> dictEntries = new LinkedHashMap<String, DICTEntry>(); 125 List<Number> operands = new ArrayList<Number>(); 126 List<Integer> operandLengths = new ArrayList<Integer>(); 127 int lastOperandLength = 0; 128 for (int i = 0; i < dictData.length; i++) { 129 int readByte = dictData[i] & 0xFF; 130 if (readByte < 28) { 131 int[] operator = new int[(readByte == DOUBLE_BYTE_OPERATOR) ? 2 : 1]; 132 if (readByte == DOUBLE_BYTE_OPERATOR) { 133 operator[0] = dictData[i]; 134 operator[1] = dictData[i + 1]; 135 i++; 136 } else { 137 operator[0] = dictData[i]; 138 } 139 String operatorName = ""; 140 CFFOperator tempOp = null; 141 if (operator.length > 1) { 142 tempOp = CFFOperator.getOperator(new CFFOperator.Key(operator[0], operator[1])); 143 } else { 144 tempOp = CFFOperator.getOperator(new CFFOperator.Key(operator[0])); 145 } 146 if (tempOp != null) { 147 operatorName = tempOp.getName(); 148 } 149 DICTEntry newEntry = new DICTEntry(); 150 newEntry.setOperator(operator); 151 newEntry.setOperands(new ArrayList<Number>(operands)); 152 newEntry.setOperatorName(operatorName); 153 newEntry.setOffset(i - lastOperandLength); 154 newEntry.setOperandLength(lastOperandLength); 155 newEntry.setOperandLengths(new ArrayList<Integer>(operandLengths)); 156 byte[] byteData = new byte[lastOperandLength + operator.length]; 157 System.arraycopy(dictData, i - operator.length - (lastOperandLength - 1), 158 byteData, 0, operator.length + lastOperandLength); 159 newEntry.setByteData(byteData); 160 dictEntries.put(operatorName, newEntry); 161 operands.clear(); 162 operandLengths.clear(); 163 lastOperandLength = 0; 164 } else { 165 if (readByte >= 32 && readByte <= 246) { 166 operands.add(readByte - 139); 167 lastOperandLength += 1; 168 operandLengths.add(1); 169 } else if (readByte >= 247 && readByte <= 250) { 170 operands.add((readByte - 247) * 256 + (dictData[i + 1] & 0xFF) + 108); 171 lastOperandLength += 2; 172 operandLengths.add(2); 173 i++; 174 } else if (readByte >= 251 && readByte <= 254) { 175 operands.add(-(readByte - 251) * 256 - (dictData[i + 1] & 0xFF) - 108); 176 lastOperandLength += 2; 177 operandLengths.add(2); 178 i++; 179 } else if (readByte == 28) { 180 operands.add((dictData[i + 1] & 0xFF) << 8 | (dictData[i + 2] & 0xFF)); 181 lastOperandLength += 3; 182 operandLengths.add(3); 183 i += 2; 184 } else if (readByte == 29) { 185 operands.add((dictData[i + 1] & 0xFF) << 24 | (dictData[i + 2] & 0xFF) << 16 186 | (dictData[i + 3] & 0xFF) << 8 | (dictData[i + 4] & 0xFF)); 187 lastOperandLength += 5; 188 operandLengths.add(5); 189 i += 4; 190 } else if (readByte == 30) { 191 boolean terminatorFound = false; 192 StringBuilder realNumber = new StringBuilder(); 193 int byteCount = 1; 194 do { 195 byte nibblesByte = dictData[++i]; 196 byteCount++; 197 terminatorFound = readNibble(realNumber, (nibblesByte >> 4) & 0x0F); 198 if (!terminatorFound) { 199 terminatorFound = readNibble(realNumber, nibblesByte & 0x0F); 200 } 201 } while (!terminatorFound); 202 operands.add(Double.valueOf(realNumber.toString())); 203 lastOperandLength += byteCount; 204 operandLengths.add(byteCount); 205 } 206 } 207 } 208 return dictEntries; 209 } 210 readNibble(StringBuilder realNumber, int nibble)211 private boolean readNibble(StringBuilder realNumber, int nibble) { 212 if (nibble <= 0x9) { 213 realNumber.append(nibble); 214 } else { 215 switch (nibble) { 216 case 0xa: realNumber.append("."); break; 217 case 0xb: realNumber.append("E"); break; 218 case 0xc: realNumber.append("E-"); break; 219 case 0xd: break; 220 case 0xe: realNumber.append("-"); break; 221 case 0xf: return true; 222 default: throw new AssertionError("Unexpected nibble value"); 223 } 224 } 225 return false; 226 } 227 228 /** 229 * A class containing data for a dictionary entry 230 */ 231 public static class DICTEntry { 232 private int[] operator; 233 private List<Number> operands; 234 private List<Integer> operandLengths; 235 private String operatorName; 236 private int offset; 237 private int operandLength; 238 private byte[] data = new byte[0]; 239 setOperator(int[] operator)240 public void setOperator(int[] operator) { 241 this.operator = operator; 242 } 243 getOperator()244 public int[] getOperator() { 245 return this.operator; 246 } 247 setOperands(List<Number> operands)248 public void setOperands(List<Number> operands) { 249 this.operands = operands; 250 } 251 getOperands()252 public List<Number> getOperands() { 253 return this.operands; 254 } 255 setOperatorName(String operatorName)256 public void setOperatorName(String operatorName) { 257 this.operatorName = operatorName; 258 } 259 getOperatorName()260 public String getOperatorName() { 261 return this.operatorName; 262 } 263 setOffset(int offset)264 public void setOffset(int offset) { 265 this.offset = offset; 266 } 267 getOffset()268 public int getOffset() { 269 return this.offset; 270 } 271 setOperandLength(int operandLength)272 public void setOperandLength(int operandLength) { 273 this.operandLength = operandLength; 274 } 275 getOperandLength()276 public int getOperandLength() { 277 return this.operandLength; 278 } 279 setByteData(byte[] data)280 public void setByteData(byte[] data) { 281 this.data = data.clone(); 282 } 283 getByteData()284 public byte[] getByteData() { 285 return data.clone(); 286 } 287 setOperandLengths(List<Integer> operandLengths)288 public void setOperandLengths(List<Integer> operandLengths) { 289 this.operandLengths = operandLengths; 290 } 291 getOperandLengths()292 public List<Integer> getOperandLengths() { 293 return operandLengths; 294 } 295 } 296 readHeader()297 private byte[] readHeader() throws IOException { 298 //Read known header 299 byte[] fixedHeader = cffData.readBytes(4); 300 int hdrSize = (fixedHeader[2] & 0xFF); 301 byte[] extra = cffData.readBytes(hdrSize - 4); 302 byte[] header = new byte[hdrSize]; 303 for (int i = 0; i < fixedHeader.length; i++) { 304 header[i] = fixedHeader[i]; 305 } 306 for (int i = 4; i < extra.length; i++) { 307 header[i] = extra[i - 4]; 308 } 309 return header; 310 } 311 312 /** 313 * Reads a CFF index object are the specified offset position 314 * @param offset The position of the index object to read 315 * @return Returns an object representing the index 316 * @throws IOException Throws an IO Exception if an error occurs 317 */ readIndex(int offset)318 public CFFIndexData readIndex(int offset) throws IOException { 319 cffData.setPosition(offset); 320 return readIndex(); 321 } 322 readIndex()323 private CFFIndexData readIndex() throws IOException { 324 return readIndex(cffData); 325 } 326 327 /** 328 * Reads an index from the current position of the CFFDataInput object 329 * @param input The object holding the CFF byte data 330 * @return Returns an object representing the index 331 * @throws IOException Throws an IO Exception if an error occurs 332 */ readIndex(CFFDataInput input)333 public CFFIndexData readIndex(CFFDataInput input) throws IOException { 334 CFFIndexData nameIndex = new CFFIndexData(); 335 if (input != null) { 336 int origPos = input.getPosition(); 337 nameIndex.parseIndexHeader(input); 338 int tableSize = input.getPosition() - origPos; 339 nameIndex.setByteData(input.getPosition() - tableSize, tableSize); 340 } 341 return nameIndex; 342 } 343 344 /** 345 * Retrieves the SID for the given GID object 346 * @param charsetOffset The offset of the charset data 347 * @param gid The GID for which to retrieve the SID 348 * @return Returns the SID as an integer 349 */ getSIDFromGID(int charsetOffset, int gid)350 public int getSIDFromGID(int charsetOffset, int gid) throws IOException { 351 if (gid == 0) { 352 return 0; 353 } 354 cffData.setPosition(charsetOffset); 355 int charsetFormat = cffData.readCard8(); 356 switch (charsetFormat) { 357 case 0: //Adjust for .notdef character 358 cffData.setPosition(cffData.getPosition() + (--gid * 2)); 359 return cffData.readSID(); 360 case 1: return getSIDFromGIDFormat(gid, 1); 361 case 2: return getSIDFromGIDFormat(gid, 2); 362 default: return 0; 363 } 364 } 365 getSIDFromGIDFormat(int gid, int format)366 private int getSIDFromGIDFormat(int gid, int format) throws IOException { 367 int glyphCount = 0; 368 while (true) { 369 int oldGlyphCount = glyphCount; 370 int start = cffData.readSID(); 371 glyphCount += ((format == 1) ? cffData.readCard8() : cffData.readCard16()) + 1; 372 if (gid <= glyphCount) { 373 return start + (gid - oldGlyphCount) - 1; 374 } 375 } 376 } 377 getHeader()378 public byte[] getHeader() { 379 return header.clone(); 380 } 381 getNameIndex()382 public CFFIndexData getNameIndex() { 383 return nameIndex; 384 } 385 getTopDictIndex()386 public CFFIndexData getTopDictIndex() { 387 return topDICTIndex; 388 } 389 getTopDictEntries()390 public LinkedHashMap<String, DICTEntry> getTopDictEntries() { 391 return topDict; 392 } 393 getStringIndex()394 public CFFIndexData getStringIndex() { 395 return stringIndex; 396 } 397 getGlobalIndexSubr()398 public CFFIndexData getGlobalIndexSubr() { 399 return globalIndexSubr; 400 } 401 getLocalIndexSubr()402 public CFFIndexData getLocalIndexSubr() { 403 return localIndexSubr; 404 } 405 getCharStringIndex()406 public CFFIndexData getCharStringIndex() { 407 return charStringIndex; 408 } 409 getCFFData()410 public CFFDataInput getCFFData() { 411 return cffData; 412 } 413 getEncoding()414 public CustomEncoding getEncoding() { 415 return encoding; 416 } 417 getFDSelect()418 public FDSelect getFDSelect() { 419 return fdSelect; 420 } 421 getFDFonts()422 public List<FontDict> getFDFonts() { 423 return fdFonts; 424 } 425 getLocalSubrsForGlyph(int glyph)426 public CFFDataInput getLocalSubrsForGlyph(int glyph) throws IOException { 427 //Subsets are currently written using a Format0 FDSelect 428 FDSelect fontDictionary = getFDSelect(); 429 if (fontDictionary instanceof Format0FDSelect) { 430 Format0FDSelect fdSelect = (Format0FDSelect)fontDictionary; 431 int found = fdSelect.getFDIndexes()[glyph]; 432 FontDict font = getFDFonts().get(found); 433 byte[] localSubrData = font.getLocalSubrData().getByteData(); 434 if (localSubrData != null) { 435 return new CFFDataInput(localSubrData); 436 } else { 437 return null; 438 } 439 } else if (fontDictionary instanceof Format3FDSelect) { 440 Format3FDSelect fdSelect = (Format3FDSelect)fontDictionary; 441 int index = 0; 442 for (int first : fdSelect.getRanges().keySet()) { 443 if (first > glyph) { 444 break; 445 } 446 index++; 447 } 448 FontDict font = getFDFonts().get(index); 449 byte[] localSubrsData = font.getLocalSubrData().getByteData(); 450 if (localSubrsData != null) { 451 return new CFFDataInput(localSubrsData); 452 } else { 453 return null; 454 } 455 } 456 return null; 457 } 458 459 /** 460 * Parses the char string index from the CFF byte data 461 * @return Returns the char string index object 462 * @throws IOException Throws an IO Exception if an error occurs 463 */ readCharStringIndex()464 public CFFIndexData readCharStringIndex() throws IOException { 465 int offset = topDict.get("CharStrings").getOperands().get(0).intValue(); 466 cffData.setPosition(offset); 467 return readIndex(); 468 } 469 readEncoding()470 private CustomEncoding readEncoding() throws IOException { 471 CustomEncoding foundEncoding = null; 472 if (topDict.get("Encoding") != null) { 473 int offset = topDict.get("Encoding").getOperands().get(0).intValue(); 474 if (offset != 0 && offset != 1) { 475 //No need to set the offset as we are reading the data sequentially. 476 int format = cffData.readCard8(); 477 int numEntries = cffData.readCard8(); 478 switch (format) { 479 case 0: 480 foundEncoding = readFormat0Encoding(format, numEntries); 481 break; 482 case 1: 483 foundEncoding = readFormat1Encoding(format, numEntries); 484 break; 485 default: break; 486 } 487 } 488 } 489 return foundEncoding; 490 } 491 readFormat0Encoding(int format, int numEntries)492 private Format0Encoding readFormat0Encoding(int format, int numEntries) 493 throws IOException { 494 Format0Encoding newEncoding = new Format0Encoding(); 495 newEncoding.setFormat(format); 496 newEncoding.setNumEntries(numEntries); 497 int[] codes = new int[numEntries]; 498 for (int i = 0; i < numEntries; i++) { 499 codes[i] = cffData.readCard8(); 500 } 501 newEncoding.setCodes(codes); 502 return newEncoding; 503 } 504 readFormat1Encoding(int format, int numEntries)505 private Format1Encoding readFormat1Encoding(int format, int numEntries) 506 throws IOException { 507 Format1Encoding newEncoding = new Format1Encoding(); 508 newEncoding.setFormat(format); 509 newEncoding.setNumEntries(numEntries); 510 Map<Integer, Integer> ranges = new LinkedHashMap<Integer, Integer>(); 511 for (int i = 0; i < numEntries; i++) { 512 int first = cffData.readCard8(); 513 int left = cffData.readCard8(); 514 ranges.put(first, left); 515 } 516 newEncoding.setRanges(ranges); 517 return newEncoding; 518 } 519 readFDSelect()520 private FDSelect readFDSelect() throws IOException { 521 FDSelect fdSelect = null; 522 DICTEntry fdSelectEntry = topDict.get("FDSelect"); 523 if (fdSelectEntry != null) { 524 int fdOffset = fdSelectEntry.getOperands().get(0).intValue(); 525 cffData.setPosition(fdOffset); 526 int format = cffData.readCard8(); 527 switch (format) { 528 case 0: 529 fdSelect = readFormat0FDSelect(); 530 break; 531 case 3: 532 fdSelect = readFormat3FDSelect(); 533 break; 534 default: 535 } 536 } 537 return fdSelect; 538 } 539 readFormat0FDSelect()540 private Format0FDSelect readFormat0FDSelect() throws IOException { 541 Format0FDSelect newFDs = new Format0FDSelect(); 542 newFDs.setFormat(0); 543 int glyphCount = charStringIndex.getNumObjects(); 544 int[] fds = new int[glyphCount]; 545 for (int i = 0; i < glyphCount; i++) { 546 fds[i] = cffData.readCard8(); 547 } 548 newFDs.setFDIndexes(fds); 549 return newFDs; 550 } 551 readFormat3FDSelect()552 private Format3FDSelect readFormat3FDSelect() throws IOException { 553 Format3FDSelect newFDs = new Format3FDSelect(); 554 newFDs.setFormat(3); 555 int rangeCount = cffData.readCard16(); 556 newFDs.setRangeCount(rangeCount); 557 Map<Integer, Integer> ranges = new LinkedHashMap<Integer, Integer>(); 558 for (int i = 0; i < rangeCount; i++) { 559 int first = cffData.readCard16(); 560 int fd = cffData.readCard8(); 561 ranges.put(first, fd); 562 } 563 newFDs.setRanges(ranges); 564 newFDs.setSentinelGID(cffData.readCard16()); 565 return newFDs; 566 } 567 parseCIDData()568 private List<FontDict> parseCIDData() throws IOException { 569 List<FontDict> fdFonts = new ArrayList<FontDict>(); 570 if (topDict.get("ROS") != null) { 571 DICTEntry fdArray = topDict.get("FDArray"); 572 if (fdArray != null) { 573 int fdIndex = fdArray.getOperands().get(0).intValue(); 574 CFFIndexData fontDicts = readIndex(fdIndex); 575 for (int i = 0; i < fontDicts.getNumObjects(); i++) { 576 FontDict newFontDict = new FontDict(); 577 578 byte[] fdData = fontDicts.getValue(i); 579 Map<String, DICTEntry> fdEntries = parseDictData(fdData); 580 newFontDict.setByteData(fontDicts.getValuePosition(i), fontDicts.getValueLength(i)); 581 DICTEntry fontFDEntry = fdEntries.get("FontName"); 582 if (fontFDEntry != null) { 583 newFontDict.setFontName(getString(fontFDEntry.getOperands().get(0).intValue())); 584 } 585 DICTEntry privateFDEntry = fdEntries.get("Private"); 586 if (privateFDEntry != null) { 587 newFontDict = setFDData(privateFDEntry, newFontDict); 588 } 589 590 fdFonts.add(newFontDict); 591 } 592 } 593 } 594 return fdFonts; 595 } 596 setFDData(DICTEntry privateFDEntry, FontDict newFontDict)597 private FontDict setFDData(DICTEntry privateFDEntry, FontDict newFontDict) throws IOException { 598 int privateFDLength = privateFDEntry.getOperands().get(0).intValue(); 599 int privateFDOffset = privateFDEntry.getOperands().get(1).intValue(); 600 cffData.setPosition(privateFDOffset); 601 byte[] privateDict = cffData.readBytes(privateFDLength); 602 newFontDict.setPrivateDictData(privateFDOffset, privateFDLength); 603 Map<String, DICTEntry> privateEntries = parseDictData(privateDict); 604 DICTEntry subroutines = privateEntries.get("Subrs"); 605 if (subroutines != null) { 606 CFFIndexData localSubrs = readIndex(privateFDOffset 607 + subroutines.getOperands().get(0).intValue()); 608 newFontDict.setLocalSubrData(localSubrs); 609 } else { 610 newFontDict.setLocalSubrData(new CFFIndexData()); 611 } 612 return newFontDict; 613 } 614 getString(int sid)615 private String getString(int sid) throws IOException { 616 return new String(stringIndex.getValue(sid - NUM_STANDARD_STRINGS)); 617 } 618 readLocalIndexSubrs()619 private CFFIndexData readLocalIndexSubrs() throws IOException { 620 CFFIndexData localSubrs = null; 621 DICTEntry privateEntry = topDict.get("Private"); 622 if (privateEntry != null) { 623 int length = privateEntry.getOperands().get(0).intValue(); 624 int offset = privateEntry.getOperands().get(1).intValue(); 625 cffData.setPosition(offset); 626 byte[] privateData = cffData.readBytes(length); 627 Map<String, DICTEntry> privateDict = parseDictData(privateData); 628 DICTEntry localSubrsEntry = privateDict.get("Subrs"); 629 if (localSubrsEntry != null) { 630 int localOffset = offset + localSubrsEntry.getOperands().get(0).intValue(); 631 cffData.setPosition(localOffset); 632 localSubrs = readIndex(); 633 } 634 } 635 return localSubrs; 636 } 637 638 /** 639 * Parent class which provides the ability to retrieve byte data from 640 * a sub-table. 641 */ 642 public class CFFSubTable { 643 private DataLocation dataLocation = new DataLocation(); 644 setByteData(int position, int length)645 public void setByteData(int position, int length) { 646 dataLocation = new DataLocation(position, length); 647 } 648 getByteData()649 public byte[] getByteData() throws IOException { 650 int oldPos = cffData.getPosition(); 651 try { 652 cffData.setPosition(dataLocation.getDataPosition()); 653 return cffData.readBytes(dataLocation.getDataLength()); 654 } finally { 655 cffData.setPosition(oldPos); 656 } 657 } 658 } 659 660 /** 661 * An object used to hold index data from the CFF data 662 */ 663 public class CFFIndexData extends CFFSubTable { 664 private int numObjects; 665 private int offSize; 666 private int[] offsets = new int[0]; 667 private DataLocation dataLocation = new DataLocation(); 668 setNumObjects(int numObjects)669 public void setNumObjects(int numObjects) { 670 this.numObjects = numObjects; 671 } 672 getNumObjects()673 public int getNumObjects() { 674 return this.numObjects; 675 } 676 setOffSize(int offSize)677 public void setOffSize(int offSize) { 678 this.offSize = offSize; 679 } 680 getOffSize()681 public int getOffSize() { 682 return this.offSize; 683 } 684 setOffsets(int[] offsets)685 public void setOffsets(int[] offsets) { 686 this.offsets = offsets.clone(); 687 } 688 getOffsets()689 public int[] getOffsets() { 690 return offsets.clone(); 691 } 692 setData(int position, int length)693 public void setData(int position, int length) { 694 dataLocation = new DataLocation(position, length); 695 } 696 getData()697 public byte[] getData() throws IOException { 698 int origPos = cffData.getPosition(); 699 try { 700 cffData.setPosition(dataLocation.getDataPosition()); 701 return cffData.readBytes(dataLocation.getDataLength()); 702 } finally { 703 cffData.setPosition(origPos); 704 } 705 } 706 707 /** 708 * Parses index data from an index object found within the CFF byte data 709 * @param cffData A byte array containing the CFF data 710 * @throws IOException Throws an IO Exception if an error occurs 711 */ parseIndexHeader(CFFDataInput cffData)712 public void parseIndexHeader(CFFDataInput cffData) throws IOException { 713 setNumObjects(cffData.readCard16()); 714 setOffSize(cffData.readOffSize()); 715 int[] offsets = new int[getNumObjects() + 1]; 716 byte[] bytes; 717 //Fills the offsets array 718 for (int i = 0; i <= getNumObjects(); i++) { 719 switch (getOffSize()) { 720 case 1: 721 offsets[i] = cffData.readCard8(); 722 break; 723 case 2: 724 offsets[i] = cffData.readCard16(); 725 break; 726 case 3: 727 bytes = cffData.readBytes(3); 728 offsets[i] = ((bytes[0] & 0xFF) << 16) + ((bytes[1] & 0xFF) << 8) + (bytes[2] & 0xFF); 729 break; 730 case 4: 731 bytes = cffData.readBytes(4); 732 offsets[i] = ((bytes[0] & 0xFF) << 24) + ((bytes[1] & 0xFF) << 16) 733 + ((bytes[2] & 0xFF) << 8) + (bytes[3] & 0xFF); 734 break; 735 default: continue; 736 } 737 } 738 setOffsets(offsets); 739 int position = cffData.getPosition(); 740 int dataSize = offsets[offsets.length - 1] - offsets[0]; 741 742 cffData.setPosition(cffData.getPosition() + dataSize); 743 setData(position, dataSize); 744 } 745 746 /** 747 * Retrieves data from the index data 748 * @param index The index position of the data to retrieve 749 * @return Returns the byte data for the given index 750 * @throws IOException Throws an IO Exception if an error occurs 751 */ getValue(int index)752 public byte[] getValue(int index) throws IOException { 753 int oldPos = cffData.getPosition(); 754 try { 755 cffData.setPosition(dataLocation.getDataPosition() + (offsets[index] - 1)); 756 return cffData.readBytes(offsets[index + 1] - offsets[index]); 757 } finally { 758 cffData.setPosition(oldPos); 759 } 760 } 761 getValuePosition(int index)762 public int getValuePosition(int index) { 763 return dataLocation.getDataPosition() + (offsets[index] - 1); 764 } 765 getValueLength(int index)766 public int getValueLength(int index) { 767 return offsets[index + 1] - offsets[index]; 768 } 769 } 770 771 public abstract class CustomEncoding { 772 private int format; 773 private int numEntries; 774 setFormat(int format)775 public void setFormat(int format) { 776 this.format = format; 777 } 778 getFormat()779 public int getFormat() { 780 return format; 781 } 782 setNumEntries(int numEntries)783 public void setNumEntries(int numEntries) { 784 this.numEntries = numEntries; 785 } 786 getNumEntries()787 public int getNumEntries() { 788 return numEntries; 789 } 790 } 791 792 public class Format0Encoding extends CustomEncoding { 793 private int[] codes = new int[0]; 794 setCodes(int[] codes)795 public void setCodes(int[] codes) { 796 this.codes = codes.clone(); 797 } 798 getCodes()799 public int[] getCodes() { 800 return codes.clone(); 801 } 802 } 803 804 public class Format1Encoding extends CustomEncoding { 805 private Map<Integer, Integer> ranges; 806 setRanges(Map<Integer, Integer> ranges)807 public void setRanges(Map<Integer, Integer> ranges) { 808 this.ranges = ranges; 809 } 810 getRanges()811 public Map<Integer, Integer> getRanges() { 812 return ranges; 813 } 814 } 815 816 public abstract class FDSelect { 817 private int format; 818 setFormat(int format)819 public void setFormat(int format) { 820 this.format = format; 821 } 822 getFormat()823 public int getFormat() { 824 return format; 825 } 826 } 827 828 public class Format0FDSelect extends FDSelect { 829 private int[] fds = new int[0]; 830 setFDIndexes(int[] fds)831 public void setFDIndexes(int[] fds) { 832 this.fds = fds.clone(); 833 } 834 getFDIndexes()835 public int[] getFDIndexes() { 836 return fds.clone(); 837 } 838 } 839 840 public class Format3FDSelect extends FDSelect { 841 private int rangeCount; 842 private Map<Integer, Integer> ranges; 843 private int sentinelGID; 844 setRangeCount(int rangeCount)845 public void setRangeCount(int rangeCount) { 846 this.rangeCount = rangeCount; 847 } 848 getRangeCount()849 public int getRangeCount() { 850 return rangeCount; 851 } 852 setRanges(Map<Integer, Integer> ranges)853 public void setRanges(Map<Integer, Integer> ranges) { 854 this.ranges = ranges; 855 } 856 getRanges()857 public Map<Integer, Integer> getRanges() { 858 return ranges; 859 } 860 setSentinelGID(int sentinelGID)861 public void setSentinelGID(int sentinelGID) { 862 this.sentinelGID = sentinelGID; 863 } 864 getSentinelGID()865 public int getSentinelGID() { 866 return sentinelGID; 867 } 868 } 869 870 public class FontDict extends CFFSubTable { 871 private String fontName; 872 private DataLocation dataLocation = new DataLocation(); 873 private CFFIndexData localSubrData; 874 setFontName(String groupName)875 public void setFontName(String groupName) { 876 this.fontName = groupName; 877 } 878 getFontName()879 public String getFontName() { 880 return fontName; 881 } 882 setPrivateDictData(int position, int length)883 public void setPrivateDictData(int position, int length) { 884 dataLocation = new DataLocation(position, length); 885 } 886 getPrivateDictData()887 public byte[] getPrivateDictData() throws IOException { 888 int origPos = cffData.getPosition(); 889 try { 890 cffData.setPosition(dataLocation.getDataPosition()); 891 return cffData.readBytes(dataLocation.getDataLength()); 892 } finally { 893 cffData.setPosition(origPos); 894 } 895 } 896 setLocalSubrData(CFFIndexData localSubrData)897 public void setLocalSubrData(CFFIndexData localSubrData) { 898 this.localSubrData = localSubrData; 899 } 900 getLocalSubrData()901 public CFFIndexData getLocalSubrData() { 902 return localSubrData; 903 } 904 } 905 906 private static class DataLocation { 907 private int dataPosition; 908 private int dataLength; 909 DataLocation()910 public DataLocation() { 911 dataPosition = 0; 912 dataLength = 0; 913 } 914 DataLocation(int position, int length)915 public DataLocation(int position, int length) { 916 this.dataPosition = position; 917 this.dataLength = length; 918 } 919 getDataPosition()920 public int getDataPosition() { 921 return dataPosition; 922 } 923 getDataLength()924 public int getDataLength() { 925 return dataLength; 926 } 927 } 928 } 929