You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

CFFDataReader.java 32KB

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