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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929
  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. if (fontFDEntry != null) {
  522. newFontDict.setFontName(getString(fontFDEntry.getOperands().get(0).intValue()));
  523. }
  524. DICTEntry privateFDEntry = fdEntries.get("Private");
  525. if (privateFDEntry != null) {
  526. newFontDict = setFDData(privateFDEntry, newFontDict);
  527. }
  528. fdFonts.add(newFontDict);
  529. }
  530. }
  531. }
  532. return fdFonts;
  533. }
  534. private FontDict setFDData(DICTEntry privateFDEntry, FontDict newFontDict) throws IOException {
  535. int privateFDLength = privateFDEntry.getOperands().get(0).intValue();
  536. int privateFDOffset = privateFDEntry.getOperands().get(1).intValue();
  537. cffData.setPosition(privateFDOffset);
  538. byte[] privateDict = cffData.readBytes(privateFDLength);
  539. newFontDict.setPrivateDictData(privateFDOffset, privateFDLength);
  540. LinkedHashMap<String, DICTEntry> privateEntries = parseDictData(privateDict);
  541. DICTEntry subroutines = privateEntries.get("Subrs");
  542. if (subroutines != null) {
  543. CFFIndexData localSubrs = readIndex(privateFDOffset
  544. + subroutines.getOperands().get(0).intValue());
  545. newFontDict.setLocalSubrData(localSubrs);
  546. } else {
  547. newFontDict.setLocalSubrData(new CFFIndexData());
  548. }
  549. return newFontDict;
  550. }
  551. private String getString(int sid) throws IOException {
  552. return new String(stringIndex.getValue(sid - NUM_STANDARD_STRINGS));
  553. }
  554. private CFFIndexData readLocalIndexSubrs() throws IOException {
  555. CFFIndexData localSubrs = null;
  556. DICTEntry privateEntry = topDict.get("Private");
  557. if (privateEntry != null) {
  558. int length = privateEntry.getOperands().get(0).intValue();
  559. int offset = privateEntry.getOperands().get(1).intValue();
  560. cffData.setPosition(offset);
  561. byte[] privateData = cffData.readBytes(length);
  562. LinkedHashMap<String, DICTEntry> privateDict = parseDictData(privateData);
  563. DICTEntry localSubrsEntry = privateDict.get("Subrs");
  564. if (localSubrsEntry != null) {
  565. int localOffset = offset + localSubrsEntry.getOperands().get(0).intValue();
  566. cffData.setPosition(localOffset);
  567. localSubrs = readIndex();
  568. }
  569. }
  570. return localSubrs;
  571. }
  572. /**
  573. * Parent class which provides the ability to retrieve byte data from
  574. * a sub-table.
  575. */
  576. public class CFFSubTable {
  577. private DataLocation dataLocation = new DataLocation();
  578. public void setByteData(int position, int length) {
  579. dataLocation = new DataLocation(position, length);
  580. }
  581. public byte[] getByteData() throws IOException {
  582. int oldPos = cffData.getPosition();
  583. try {
  584. cffData.setPosition(dataLocation.getDataPosition());
  585. return cffData.readBytes(dataLocation.getDataLength());
  586. } finally {
  587. cffData.setPosition(oldPos);
  588. }
  589. }
  590. }
  591. /**
  592. * An object used to hold index data from the CFF data
  593. */
  594. public class CFFIndexData extends CFFSubTable {
  595. private int numObjects;
  596. private int offSize;
  597. private int[] offsets = new int[0];
  598. private DataLocation dataLocation = new DataLocation();
  599. public void setNumObjects(int numObjects) {
  600. this.numObjects = numObjects;
  601. }
  602. public int getNumObjects() {
  603. return this.numObjects;
  604. }
  605. public void setOffSize(int offSize) {
  606. this.offSize = offSize;
  607. }
  608. public int getOffSize() {
  609. return this.offSize;
  610. }
  611. public void setOffsets(int[] offsets) {
  612. this.offsets = offsets.clone();
  613. }
  614. public int[] getOffsets() {
  615. return offsets.clone();
  616. }
  617. public void setData(int position, int length) {
  618. dataLocation = new DataLocation(position, length);
  619. }
  620. public byte[] getData() throws IOException {
  621. int origPos = cffData.getPosition();
  622. try {
  623. cffData.setPosition(dataLocation.getDataPosition());
  624. return cffData.readBytes(dataLocation.getDataLength());
  625. } finally {
  626. cffData.setPosition(origPos);
  627. }
  628. }
  629. /**
  630. * Parses index data from an index object found within the CFF byte data
  631. * @param cffData A byte array containing the CFF data
  632. * @throws IOException Throws an IO Exception if an error occurs
  633. */
  634. public void parseIndexHeader(CFFDataInput cffData) throws IOException {
  635. setNumObjects(cffData.readCard16());
  636. setOffSize(cffData.readOffSize());
  637. int[] offsets = new int[getNumObjects() + 1];
  638. byte[] bytes;
  639. //Fills the offsets array
  640. for (int i = 0; i <= getNumObjects(); i++) {
  641. switch (getOffSize()) {
  642. case 1:
  643. offsets[i] = cffData.readCard8();
  644. break;
  645. case 2:
  646. offsets[i] = cffData.readCard16();
  647. break;
  648. case 3:
  649. bytes = cffData.readBytes(3);
  650. offsets[i] = ((bytes[0] & 0xFF) << 16) + ((bytes[1] & 0xFF) << 8) + (bytes[2] & 0xFF);
  651. break;
  652. case 4:
  653. bytes = cffData.readBytes(4);
  654. offsets[i] = ((bytes[0] & 0xFF) << 24) + ((bytes[1] & 0xFF) << 16)
  655. + ((bytes[2] & 0xFF) << 8) + (bytes[3] & 0xFF);
  656. break;
  657. default: continue;
  658. }
  659. }
  660. setOffsets(offsets);
  661. int position = cffData.getPosition();
  662. int dataSize = offsets[offsets.length - 1] - offsets[0];
  663. cffData.setPosition(cffData.getPosition() + dataSize);
  664. setData(position, dataSize);
  665. }
  666. /**
  667. * Retrieves data from the index data
  668. * @param index The index position of the data to retrieve
  669. * @return Returns the byte data for the given index
  670. * @throws IOException Throws an IO Exception if an error occurs
  671. */
  672. public byte[] getValue(int index) throws IOException {
  673. int oldPos = cffData.getPosition();
  674. try {
  675. cffData.setPosition(dataLocation.getDataPosition() + (offsets[index] - 1));
  676. return cffData.readBytes(offsets[index + 1] - offsets[index]);
  677. } finally {
  678. cffData.setPosition(oldPos);
  679. }
  680. }
  681. public int getValuePosition(int index) {
  682. return dataLocation.getDataPosition() + (offsets[index] - 1);
  683. }
  684. public int getValueLength(int index) {
  685. return offsets[index + 1] - offsets[index];
  686. }
  687. }
  688. public abstract class CustomEncoding {
  689. private int format;
  690. private int numEntries;
  691. public void setFormat(int format) {
  692. this.format = format;
  693. }
  694. public int getFormat() {
  695. return format;
  696. }
  697. public void setNumEntries(int numEntries) {
  698. this.numEntries = numEntries;
  699. }
  700. public int getNumEntries() {
  701. return numEntries;
  702. }
  703. }
  704. public class Format0Encoding extends CustomEncoding {
  705. private int[] codes = new int[0];
  706. public void setCodes(int[] codes) {
  707. this.codes = codes.clone();
  708. }
  709. public int[] getCodes() {
  710. return codes.clone();
  711. }
  712. }
  713. public class Format1Encoding extends CustomEncoding {
  714. private LinkedHashMap<Integer, Integer> ranges;
  715. public void setRanges(LinkedHashMap<Integer, Integer> ranges) {
  716. this.ranges = ranges;
  717. }
  718. public LinkedHashMap<Integer, Integer> getRanges() {
  719. return ranges;
  720. }
  721. }
  722. public class FDSelect {
  723. private int format;
  724. public void setFormat(int format) {
  725. this.format = format;
  726. }
  727. public int getFormat() {
  728. return format;
  729. }
  730. }
  731. public class Format0FDSelect extends FDSelect {
  732. private int[] fds = new int[0];
  733. public void setFDIndexes(int[] fds) {
  734. this.fds = fds.clone();
  735. }
  736. public int[] getFDIndexes() {
  737. return fds.clone();
  738. }
  739. }
  740. public class Format3FDSelect extends FDSelect {
  741. private int rangeCount;
  742. private LinkedHashMap<Integer, Integer> ranges;
  743. private int sentinelGID;
  744. public void setRangeCount(int rangeCount) {
  745. this.rangeCount = rangeCount;
  746. }
  747. public int getRangeCount() {
  748. return rangeCount;
  749. }
  750. public void setRanges(LinkedHashMap<Integer, Integer> ranges) {
  751. this.ranges = ranges;
  752. }
  753. public LinkedHashMap<Integer, Integer> getRanges() {
  754. return ranges;
  755. }
  756. public void setSentinelGID(int sentinelGID) {
  757. this.sentinelGID = sentinelGID;
  758. }
  759. public int getSentinelGID() {
  760. return sentinelGID;
  761. }
  762. }
  763. public class FontDict extends CFFSubTable {
  764. private String fontName;
  765. private DataLocation dataLocation = new DataLocation();
  766. private CFFIndexData localSubrData;
  767. public void setFontName(String groupName) {
  768. this.fontName = groupName;
  769. }
  770. public String getFontName() {
  771. return fontName;
  772. }
  773. public void setPrivateDictData(int position, int length) {
  774. dataLocation = new DataLocation(position, length);
  775. }
  776. public byte[] getPrivateDictData() throws IOException {
  777. int origPos = cffData.getPosition();
  778. try {
  779. cffData.setPosition(dataLocation.getDataPosition());
  780. return cffData.readBytes(dataLocation.getDataLength());
  781. } finally {
  782. cffData.setPosition(origPos);
  783. }
  784. }
  785. public void setLocalSubrData(CFFIndexData localSubrData) {
  786. this.localSubrData = localSubrData;
  787. }
  788. public CFFIndexData getLocalSubrData() {
  789. return localSubrData;
  790. }
  791. }
  792. private static class DataLocation {
  793. private int dataPosition;
  794. private int dataLength;
  795. public DataLocation() {
  796. dataPosition = 0;
  797. dataLength = 0;
  798. }
  799. public DataLocation(int position, int length) {
  800. this.dataPosition = position;
  801. this.dataLength = length;
  802. }
  803. public int getDataPosition() {
  804. return dataPosition;
  805. }
  806. public int getDataLength() {
  807. return dataLength;
  808. }
  809. }
  810. }