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.

OTFSubSetFile.java 44KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097
  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.truetype;
  19. import java.io.IOException;
  20. import java.util.ArrayList;
  21. import java.util.Arrays;
  22. import java.util.Collections;
  23. import java.util.Comparator;
  24. import java.util.HashMap;
  25. import java.util.LinkedHashMap;
  26. import java.util.List;
  27. import java.util.Map;
  28. import java.util.Map.Entry;
  29. import org.apache.fontbox.cff.CFFStandardString;
  30. import org.apache.fontbox.cff.encoding.CFFEncoding;
  31. import org.apache.fop.fonts.MultiByteFont;
  32. import org.apache.fop.fonts.cff.CFFDataReader;
  33. import org.apache.fop.fonts.cff.CFFDataReader.CFFIndexData;
  34. import org.apache.fop.fonts.cff.CFFDataReader.DICTEntry;
  35. import org.apache.fop.fonts.cff.CFFDataReader.FDSelect;
  36. import org.apache.fop.fonts.cff.CFFDataReader.FontDict;
  37. import org.apache.fop.fonts.cff.CFFDataReader.Format0FDSelect;
  38. import org.apache.fop.fonts.cff.CFFDataReader.Format3FDSelect;
  39. /**
  40. * Reads an OpenType CFF file and generates a subset
  41. * The OpenType specification can be found at the Microsoft
  42. * Typography site: http://www.microsoft.com/typography/otspec/
  43. */
  44. public class OTFSubSetFile extends OTFFile {
  45. private byte[] output;
  46. private int currentPos = 0;
  47. private int realSize = 0;
  48. /** A map containing each glyph to be included in the subset
  49. * with their existing and new GID's **/
  50. private LinkedHashMap<Integer, Integer> subsetGlyphs;
  51. /** A map of the new GID to SID used to construct the charset table **/
  52. private LinkedHashMap<Integer, Integer> gidToSID;
  53. private CFFIndexData localIndexSubr;
  54. private CFFIndexData globalIndexSubr;
  55. /** List of subroutines to write to the local / global indexes in the subset font **/
  56. private List<byte[]> subsetLocalIndexSubr;
  57. private List<byte[]> subsetGlobalIndexSubr;
  58. /** For fonts which have an FDSelect or ROS flag in Top Dict, this is used to store the
  59. * local subroutine indexes for each group as opposed to the above subsetLocalIndexSubr */
  60. private ArrayList<List<byte[]>> fdSubrs;
  61. /** The subset FD Select table used to store the mappings between glyphs and their
  62. * associated FDFont object which point to a private dict and local subroutines. */
  63. private LinkedHashMap<Integer, FDIndexReference> subsetFDSelect;
  64. /** A list of unique subroutines from the global / local subroutine indexes */
  65. private List<Integer> localUniques;
  66. private List<Integer> globalUniques;
  67. /** A store of the number of subroutines each global / local subroutine will store **/
  68. private int subsetLocalSubrCount;
  69. private int subsetGlobalSubrCount;
  70. /** A list of char string data for each glyph to be stored in the subset font **/
  71. private List<byte[]> subsetCharStringsIndex;
  72. /** The embedded name to change in the name table **/
  73. private String embeddedName;
  74. /** An array used to hold the string index data for the subset font **/
  75. private List<byte[]> stringIndexData = new ArrayList<byte[]>();
  76. /** The CFF reader object used to read data and offsets from the original font file */
  77. private CFFDataReader cffReader = null;
  78. /** The class used to represent this font **/
  79. private MultiByteFont mbFont;
  80. /** The number of standard strings in CFF **/
  81. private static final int NUM_STANDARD_STRINGS = 391;
  82. /** The operator used to identify a local subroutine reference */
  83. private static final int LOCAL_SUBROUTINE = 10;
  84. /** The operator used to identify a global subroutine reference */
  85. private static final int GLOBAL_SUBROUTINE = 29;
  86. public OTFSubSetFile() throws IOException {
  87. super();
  88. }
  89. public void readFont(FontFileReader in, String embeddedName, String header,
  90. MultiByteFont mbFont) throws IOException {
  91. this.mbFont = mbFont;
  92. readFont(in, embeddedName, header, mbFont.getUsedGlyphs());
  93. }
  94. /**
  95. * Reads and creates a subset of the font.
  96. *
  97. * @param in FontFileReader to read from
  98. * @param name Name to be checked for in the font file
  99. * @param header The header of the font file
  100. * @param glyphs Map of glyphs (glyphs has old index as (Integer) key and
  101. * new index as (Integer) value)
  102. * @throws IOException in case of an I/O problem
  103. */
  104. void readFont(FontFileReader in, String embeddedName, String header,
  105. Map<Integer, Integer> usedGlyphs) throws IOException {
  106. fontFile = in;
  107. currentPos = 0;
  108. realSize = 0;
  109. this.embeddedName = embeddedName;
  110. //Sort by the new GID and store in a LinkedHashMap
  111. subsetGlyphs = sortByValue(usedGlyphs);
  112. output = new byte[in.getFileSize()];
  113. initializeFont(in);
  114. cffReader = new CFFDataReader(fontFile);
  115. //Create the CIDFontType0C data
  116. createCFF();
  117. }
  118. private LinkedHashMap<Integer, Integer> sortByValue(Map<Integer, Integer> map) {
  119. List<Entry<Integer, Integer>> list = new ArrayList<Entry<Integer, Integer>>(map.entrySet());
  120. Collections.sort(list, new Comparator<Entry<Integer, Integer>>() {
  121. public int compare(Entry<Integer, Integer> o1, Entry<Integer, Integer> o2) {
  122. return ((Comparable<Integer>) o1.getValue()).compareTo(o2.getValue());
  123. }
  124. });
  125. LinkedHashMap<Integer, Integer> result = new LinkedHashMap<Integer, Integer>();
  126. for (Entry<Integer, Integer> entry : list) {
  127. result.put(entry.getKey(), entry.getValue());
  128. }
  129. return result;
  130. }
  131. private void createCFF() throws IOException {
  132. //Header
  133. writeBytes(cffReader.getHeader());
  134. //Name Index
  135. writeIndex(Arrays.asList(embeddedName.getBytes()));
  136. //Keep offset of the topDICT so it can be updated once all data has been written
  137. int topDictOffset = currentPos;
  138. //Top DICT Index and Data
  139. byte[] topDictIndex = cffReader.getTopDictIndex().getByteData();
  140. int offSize = topDictIndex[2];
  141. writeBytes(topDictIndex, 0, 3 + (offSize * 2));
  142. int topDictDataOffset = currentPos;
  143. writeTopDICT();
  144. //Create the char string index data and related local / global subroutines
  145. if (cffReader.getFDSelect() == null) {
  146. createCharStringData();
  147. } else {
  148. createCharStringDataCID();
  149. }
  150. //If it is a CID-Keyed font, store each FD font and add each SID
  151. List<Integer> fontNameSIDs = null;
  152. List<Integer> subsetFDFonts = null;
  153. if (cffReader.getFDSelect() != null) {
  154. subsetFDFonts = getUsedFDFonts();
  155. fontNameSIDs = storeFDStrings(subsetFDFonts);
  156. }
  157. //String index
  158. writeStringIndex();
  159. //Global subroutine index
  160. writeIndex(subsetGlobalIndexSubr);
  161. //Encoding
  162. int encodingOffset = currentPos;
  163. writeEncoding(fileFont.getEncoding());
  164. //Charset table
  165. int charsetOffset = currentPos;
  166. writeCharsetTable(cffReader.getFDSelect() != null);
  167. //FDSelect table
  168. int fdSelectOffset = currentPos;
  169. if (cffReader.getFDSelect() != null) {
  170. writeFDSelect();
  171. }
  172. //Char Strings Index
  173. int charStringOffset = currentPos;
  174. writeIndex(subsetCharStringsIndex);
  175. if (cffReader.getFDSelect() == null) {
  176. //Keep offset to modify later with the local subroutine index offset
  177. int privateDictOffset = currentPos;
  178. writePrivateDict();
  179. //Local subroutine index
  180. int localIndexOffset = currentPos;
  181. writeIndex(subsetLocalIndexSubr);
  182. //Update the offsets
  183. updateOffsets(topDictOffset, charsetOffset, charStringOffset, privateDictOffset,
  184. localIndexOffset, encodingOffset);
  185. } else {
  186. List<Integer> privateDictOffsets = writeCIDDictsAndSubrs(subsetFDFonts);
  187. int fdArrayOffset = writeFDArray(subsetFDFonts, privateDictOffsets, fontNameSIDs);
  188. updateCIDOffsets(topDictDataOffset, fdArrayOffset, fdSelectOffset, charsetOffset,
  189. charStringOffset, encodingOffset);
  190. }
  191. }
  192. private List<Integer> storeFDStrings(List<Integer> uniqueNewRefs) throws IOException {
  193. ArrayList<Integer> fontNameSIDs = new ArrayList<Integer>();
  194. List<FontDict> fdFonts = cffReader.getFDFonts();
  195. for (int i = 0; i < uniqueNewRefs.size(); i++) {
  196. FontDict fdFont = fdFonts.get(uniqueNewRefs.get(i));
  197. byte[] fdFontByteData = fdFont.getByteData();
  198. Map<String, DICTEntry> fdFontDict = cffReader.parseDictData(fdFontByteData);
  199. fontNameSIDs.add(stringIndexData.size() + NUM_STANDARD_STRINGS);
  200. stringIndexData.add(cffReader.getStringIndex().getValue(fdFontDict.get("FontName")
  201. .getOperands().get(0).intValue() - NUM_STANDARD_STRINGS));
  202. }
  203. return fontNameSIDs;
  204. }
  205. private void writeBytes(byte[] out) {
  206. for (int i = 0; i < out.length; i++) {
  207. output[currentPos++] = out[i];
  208. realSize++;
  209. }
  210. }
  211. private void writeBytes(byte[] out, int offset, int length) {
  212. for (int i = offset; i < offset + length; i++) {
  213. output[currentPos++] = out[i];
  214. realSize++;
  215. }
  216. }
  217. private void writeEncoding(CFFEncoding encoding) throws IOException {
  218. LinkedHashMap<String, DICTEntry> topDICT = cffReader.getTopDictEntries();
  219. DICTEntry encodingEntry = topDICT.get("Encoding");
  220. if (encodingEntry != null && encodingEntry.getOperands().get(0).intValue() != 0
  221. && encodingEntry.getOperands().get(0).intValue() != 1) {
  222. writeByte(0);
  223. writeByte(gidToSID.size());
  224. for (int gid : gidToSID.keySet()) {
  225. int code = encoding.getCode(gidToSID.get(gid));
  226. writeByte(code);
  227. }
  228. }
  229. }
  230. private void writeTopDICT() throws IOException {
  231. LinkedHashMap<String, DICTEntry> topDICT = cffReader.getTopDictEntries();
  232. List<String> topDictStringEntries = Arrays.asList("version", "Notice", "Copyright",
  233. "FullName", "FamilyName", "Weight", "PostScript");
  234. for (Map.Entry<String, DICTEntry> dictEntry : topDICT.entrySet()) {
  235. String dictKey = dictEntry.getKey();
  236. DICTEntry entry = dictEntry.getValue();
  237. //If the value is an SID, update the reference but keep the size the same
  238. if (dictKey.equals("ROS")) {
  239. writeROSEntry(entry);
  240. } else if (dictKey.equals("CIDCount")) {
  241. writeCIDCount(entry);
  242. } else if (topDictStringEntries.contains(dictKey)) {
  243. writeTopDictStringEntry(entry);
  244. } else {
  245. writeBytes(entry.getByteData());
  246. }
  247. }
  248. }
  249. private void writeROSEntry(DICTEntry dictEntry) throws IOException {
  250. int sidA = dictEntry.getOperands().get(0).intValue();
  251. if (sidA > 390) {
  252. stringIndexData.add(cffReader.getStringIndex().getValue(sidA - NUM_STANDARD_STRINGS));
  253. }
  254. int sidAStringIndex = stringIndexData.size() + 390;
  255. int sidB = dictEntry.getOperands().get(1).intValue();
  256. if (sidB > 390) {
  257. stringIndexData.add("Identity".getBytes());
  258. }
  259. int sidBStringIndex = stringIndexData.size() + 390;
  260. byte[] cidEntryByteData = dictEntry.getByteData();
  261. cidEntryByteData = updateOffset(cidEntryByteData, 0, dictEntry.getOperandLengths().get(0),
  262. sidAStringIndex);
  263. cidEntryByteData = updateOffset(cidEntryByteData, dictEntry.getOperandLengths().get(0),
  264. dictEntry.getOperandLengths().get(1), sidBStringIndex);
  265. cidEntryByteData = updateOffset(cidEntryByteData, dictEntry.getOperandLengths().get(0)
  266. + dictEntry.getOperandLengths().get(1), dictEntry.getOperandLengths().get(2), 139);
  267. writeBytes(cidEntryByteData);
  268. }
  269. private void writeCIDCount(DICTEntry dictEntry) throws IOException {
  270. byte[] cidCountByteData = dictEntry.getByteData();
  271. cidCountByteData = updateOffset(cidCountByteData, 0, dictEntry.getOperandLengths().get(0),
  272. subsetGlyphs.size());
  273. writeBytes(cidCountByteData);
  274. }
  275. private void writeTopDictStringEntry(DICTEntry dictEntry) throws IOException {
  276. int sid = dictEntry.getOperands().get(0).intValue();
  277. if (sid > 391) {
  278. stringIndexData.add(cffReader.getStringIndex().getValue(sid - 391));
  279. }
  280. byte[] newDictEntry = createNewRef(stringIndexData.size() + 390, dictEntry.getOperator(),
  281. dictEntry.getOperandLength());
  282. writeBytes(newDictEntry);
  283. }
  284. private void writeStringIndex() throws IOException {
  285. Map<String, DICTEntry> topDICT = cffReader.getTopDictEntries();
  286. int charsetOffset = topDICT.get("charset").getOperands().get(0).intValue();
  287. gidToSID = new LinkedHashMap<Integer, Integer>();
  288. for (int gid : subsetGlyphs.keySet()) {
  289. int sid = cffReader.getSIDFromGID(charsetOffset, gid);
  290. //Check whether the SID falls into the standard string set
  291. if (sid < NUM_STANDARD_STRINGS) {
  292. gidToSID.put(subsetGlyphs.get(gid), sid);
  293. if (mbFont != null) {
  294. mbFont.mapUsedGlyphName(subsetGlyphs.get(gid),
  295. CFFStandardString.getName(sid));
  296. }
  297. } else {
  298. int index = sid - NUM_STANDARD_STRINGS;
  299. if (index <= cffReader.getStringIndex().getNumObjects()) {
  300. if (mbFont != null) {
  301. mbFont.mapUsedGlyphName(subsetGlyphs.get(gid),
  302. new String(cffReader.getStringIndex().getValue(index)));
  303. }
  304. gidToSID.put(subsetGlyphs.get(gid), stringIndexData.size() + 391);
  305. stringIndexData.add(cffReader.getStringIndex().getValue(index));
  306. } else {
  307. if (mbFont != null) {
  308. mbFont.mapUsedGlyphName(subsetGlyphs.get(gid), ".notdef");
  309. }
  310. gidToSID.put(subsetGlyphs.get(gid), index);
  311. }
  312. }
  313. }
  314. //Write the String Index
  315. writeIndex(stringIndexData);
  316. }
  317. private void createCharStringDataCID() throws IOException {
  318. CFFIndexData charStringsIndex = cffReader.getCharStringIndex();
  319. FDSelect fontDictionary = cffReader.getFDSelect();
  320. if (fontDictionary instanceof Format0FDSelect) {
  321. throw new UnsupportedOperationException("OTF CFF CID Format0 currently not implemented");
  322. } else if (fontDictionary instanceof Format3FDSelect) {
  323. Format3FDSelect fdSelect = (Format3FDSelect)fontDictionary;
  324. Map<Integer, Integer> subsetGroups = new HashMap<Integer, Integer>();
  325. List<Integer> uniqueGroups = new ArrayList<Integer>();
  326. for (int gid : subsetGlyphs.keySet()) {
  327. Integer[] ranges = fdSelect.getRanges().keySet().toArray(new Integer[0]);
  328. for (int i = 0; i < ranges.length; i++) {
  329. int nextRange = -1;
  330. if (i < ranges.length - 1) {
  331. nextRange = ranges[i + 1];
  332. } else {
  333. nextRange = fdSelect.getSentinelGID();
  334. }
  335. if (gid >= ranges[i] && gid < nextRange) {
  336. subsetGroups.put(gid, fdSelect.getRanges().get(ranges[i]));
  337. if (!uniqueGroups.contains(fdSelect.getRanges().get(ranges[i]))) {
  338. uniqueGroups.add(fdSelect.getRanges().get(ranges[i]));
  339. }
  340. }
  341. }
  342. }
  343. //Prepare resources
  344. globalIndexSubr = cffReader.getGlobalIndexSubr();
  345. //Create the new char string index
  346. subsetCharStringsIndex = new ArrayList<byte[]>();
  347. globalUniques = new ArrayList<Integer>();
  348. subsetFDSelect = new LinkedHashMap<Integer, FDIndexReference>();
  349. List<List<Integer>> foundLocalUniques = new ArrayList<List<Integer>>();
  350. for (int i = 0; i < uniqueGroups.size(); i++) {
  351. foundLocalUniques.add(new ArrayList<Integer>());
  352. }
  353. for (int gid : subsetGlyphs.keySet()) {
  354. int group = subsetGroups.get(gid);
  355. localIndexSubr = cffReader.getFDFonts().get(group).getLocalSubrData();
  356. localUniques = foundLocalUniques.get(uniqueGroups.indexOf(subsetGroups.get(gid)));
  357. FDIndexReference newFDReference = new FDIndexReference(
  358. uniqueGroups.indexOf(subsetGroups.get(gid)), subsetGroups.get(gid));
  359. subsetFDSelect.put(subsetGlyphs.get(gid), newFDReference);
  360. byte[] data = charStringsIndex.getValue(gid);
  361. preScanForSubsetIndexSize(data);
  362. }
  363. //Create the two lists which are to store the local and global subroutines
  364. subsetGlobalIndexSubr = new ArrayList<byte[]>();
  365. fdSubrs = new ArrayList<List<byte[]>>();
  366. subsetGlobalSubrCount = globalUniques.size();
  367. globalUniques.clear();
  368. localUniques = null;
  369. for (int l = 0; l < foundLocalUniques.size(); l++) {
  370. fdSubrs.add(new ArrayList<byte[]>());
  371. }
  372. List<List<Integer>> foundLocalUniquesB = new ArrayList<List<Integer>>();
  373. for (int k = 0; k < uniqueGroups.size(); k++) {
  374. foundLocalUniquesB.add(new ArrayList<Integer>());
  375. }
  376. for (Integer gid : subsetGlyphs.keySet()) {
  377. int group = subsetGroups.get(gid);
  378. localIndexSubr = cffReader.getFDFonts().get(group).getLocalSubrData();
  379. localUniques = foundLocalUniquesB.get(subsetFDSelect.get(subsetGlyphs.get(gid)).getNewFDIndex());
  380. byte[] data = charStringsIndex.getValue(gid);
  381. subsetLocalIndexSubr = fdSubrs.get(subsetFDSelect.get(subsetGlyphs.get(gid)).getNewFDIndex());
  382. subsetLocalSubrCount = foundLocalUniques.get(
  383. subsetFDSelect.get(subsetGlyphs.get(gid)).getNewFDIndex()).size();
  384. data = readCharStringData(data, subsetLocalSubrCount);
  385. subsetCharStringsIndex.add(data);
  386. }
  387. }
  388. }
  389. private void writeFDSelect() {
  390. writeByte(0); //Format
  391. for (Integer gid : subsetFDSelect.keySet()) {
  392. writeByte(subsetFDSelect.get(gid).getNewFDIndex());
  393. }
  394. }
  395. private List<Integer> getUsedFDFonts() {
  396. List<Integer> uniqueNewRefs = new ArrayList<Integer>();
  397. for (int gid : subsetFDSelect.keySet()) {
  398. int fdIndex = subsetFDSelect.get(gid).getOldFDIndex();
  399. if (!uniqueNewRefs.contains(fdIndex)) {
  400. uniqueNewRefs.add(fdIndex);
  401. }
  402. }
  403. return uniqueNewRefs;
  404. }
  405. private List<Integer> writeCIDDictsAndSubrs(List<Integer> uniqueNewRefs)
  406. throws IOException {
  407. List<Integer> privateDictOffsets = new ArrayList<Integer>();
  408. List<FontDict> fdFonts = cffReader.getFDFonts();
  409. for (int i = 0; i < uniqueNewRefs.size(); i++) {
  410. FontDict curFDFont = fdFonts.get(uniqueNewRefs.get(i));
  411. HashMap<String, DICTEntry> fdPrivateDict = cffReader.parseDictData(
  412. curFDFont.getPrivateDictData());
  413. int privateDictOffset = currentPos;
  414. privateDictOffsets.add(privateDictOffset);
  415. byte[] fdPrivateDictByteData = curFDFont.getPrivateDictData();
  416. if (fdPrivateDict.get("Subrs") != null) {
  417. fdPrivateDictByteData = updateOffset(fdPrivateDictByteData, fdPrivateDict.get("Subrs").getOffset(),
  418. fdPrivateDict.get("Subrs").getOperandLength(),
  419. fdPrivateDictByteData.length);
  420. }
  421. writeBytes(fdPrivateDictByteData);
  422. writeIndex(fdSubrs.get(i));
  423. }
  424. return privateDictOffsets;
  425. }
  426. private int writeFDArray(List<Integer> uniqueNewRefs, List<Integer> privateDictOffsets,
  427. List<Integer> fontNameSIDs)
  428. throws IOException {
  429. int offset = currentPos;
  430. List<FontDict> fdFonts = cffReader.getFDFonts();
  431. writeCard16(uniqueNewRefs.size());
  432. writeByte(1); //Offset size
  433. writeByte(1); //First offset
  434. int count = 1;
  435. for (int i = 0; i < uniqueNewRefs.size(); i++) {
  436. FontDict fdFont = fdFonts.get(uniqueNewRefs.get(i));
  437. count += fdFont.getByteData().length;
  438. writeByte(count);
  439. }
  440. for (int i = 0; i < uniqueNewRefs.size(); i++) {
  441. FontDict fdFont = fdFonts.get(uniqueNewRefs.get(i));
  442. byte[] fdFontByteData = fdFont.getByteData();
  443. Map<String, DICTEntry> fdFontDict = cffReader.parseDictData(fdFontByteData);
  444. //Update the SID to the FontName
  445. fdFontByteData = updateOffset(fdFontByteData, fdFontDict.get("FontName").getOffset() - 1,
  446. fdFontDict.get("FontName").getOperandLengths().get(0),
  447. fontNameSIDs.get(i));
  448. //Update the Private dict reference
  449. fdFontByteData = updateOffset(fdFontByteData, fdFontDict.get("Private").getOffset()
  450. + fdFontDict.get("Private").getOperandLengths().get(0),
  451. fdFontDict.get("Private").getOperandLengths().get(1),
  452. privateDictOffsets.get(i));
  453. writeBytes(fdFontByteData);
  454. }
  455. return offset;
  456. }
  457. private class FDIndexReference {
  458. private int newFDIndex;
  459. private int oldFDIndex;
  460. public FDIndexReference(int newFDIndex, int oldFDIndex) {
  461. this.newFDIndex = newFDIndex;
  462. this.oldFDIndex = oldFDIndex;
  463. }
  464. public int getNewFDIndex() {
  465. return newFDIndex;
  466. }
  467. public int getOldFDIndex() {
  468. return oldFDIndex;
  469. }
  470. }
  471. private void createCharStringData() throws IOException {
  472. Map<String, DICTEntry> topDICT = cffReader.getTopDictEntries();
  473. CFFIndexData charStringsIndex = cffReader.getCharStringIndex();
  474. DICTEntry privateEntry = topDICT.get("Private");
  475. if (privateEntry != null) {
  476. int privateOffset = privateEntry.getOperands().get(1).intValue();
  477. Map<String, DICTEntry> privateDICT = cffReader.getPrivateDict(privateEntry);
  478. if (privateDICT.get("Subrs") != null) {
  479. int localSubrOffset = privateOffset + privateDICT.get("Subrs").getOperands().get(0).intValue();
  480. localIndexSubr = cffReader.readIndex(localSubrOffset);
  481. } else {
  482. localIndexSubr = cffReader.readIndex(null);
  483. }
  484. }
  485. globalIndexSubr = cffReader.getGlobalIndexSubr();
  486. //Create the two lists which are to store the local and global subroutines
  487. subsetLocalIndexSubr = new ArrayList<byte[]>();
  488. subsetGlobalIndexSubr = new ArrayList<byte[]>();
  489. //Create the new char string index
  490. subsetCharStringsIndex = new ArrayList<byte[]>();
  491. localUniques = new ArrayList<Integer>();
  492. globalUniques = new ArrayList<Integer>();
  493. for (int gid : subsetGlyphs.keySet()) {
  494. byte[] data = charStringsIndex.getValue(gid);
  495. preScanForSubsetIndexSize(data);
  496. }
  497. //Store the size of each subset index and clear the unique arrays
  498. subsetLocalSubrCount = localUniques.size();
  499. subsetGlobalSubrCount = globalUniques.size();
  500. localUniques.clear();
  501. globalUniques.clear();
  502. for (int gid : subsetGlyphs.keySet()) {
  503. byte[] data = charStringsIndex.getValue(gid);
  504. //Retrieve modified char string data and fill local / global subroutine arrays
  505. data = readCharStringData(data, subsetLocalSubrCount);
  506. subsetCharStringsIndex.add(data);
  507. }
  508. }
  509. private void preScanForSubsetIndexSize(byte[] data) throws IOException {
  510. boolean hasLocalSubroutines = localIndexSubr != null && localIndexSubr.getNumObjects() > 0;
  511. boolean hasGlobalSubroutines = globalIndexSubr != null && globalIndexSubr.getNumObjects() > 0;
  512. BytesNumber operand = new BytesNumber(-1, -1);
  513. for (int dataPos = 0; dataPos < data.length; dataPos++) {
  514. int b0 = data[dataPos] & 0xff;
  515. if (b0 == LOCAL_SUBROUTINE && hasLocalSubroutines) {
  516. int subrNumber = getSubrNumber(localIndexSubr.getNumObjects(), operand.getNumber());
  517. if (!localUniques.contains(subrNumber) && subrNumber < localIndexSubr.getNumObjects()) {
  518. localUniques.add(subrNumber);
  519. byte[] subr = localIndexSubr.getValue(subrNumber);
  520. preScanForSubsetIndexSize(subr);
  521. }
  522. operand.clearNumber();
  523. } else if (b0 == GLOBAL_SUBROUTINE && hasGlobalSubroutines) {
  524. int subrNumber = getSubrNumber(globalIndexSubr.getNumObjects(), operand.getNumber());
  525. if (!globalUniques.contains(subrNumber) && subrNumber < globalIndexSubr.getNumObjects()) {
  526. globalUniques.add(subrNumber);
  527. byte[] subr = globalIndexSubr.getValue(subrNumber);
  528. preScanForSubsetIndexSize(subr);
  529. }
  530. operand.clearNumber();
  531. } else if ((b0 >= 0 && b0 <= 27) || (b0 >= 29 && b0 <= 31)) {
  532. operand.clearNumber();
  533. if (b0 == 19 || b0 == 20) {
  534. dataPos += 1;
  535. }
  536. } else if (b0 == 28 || (b0 >= 32 && b0 <= 255)) {
  537. operand = readNumber(b0, data, dataPos);
  538. dataPos += operand.getNumBytes() - 1;
  539. }
  540. }
  541. }
  542. private int getSubrNumber(int numSubroutines, int operand) {
  543. int bias = getBias(numSubroutines);
  544. return bias + operand;
  545. }
  546. private byte[] readCharStringData(byte[] data, int subsetLocalSubrCount) throws IOException {
  547. boolean hasLocalSubroutines = localIndexSubr != null && localIndexSubr.getNumObjects() > 0;
  548. boolean hasGlobalSubroutines = globalIndexSubr != null && globalIndexSubr.getNumObjects() > 0;
  549. BytesNumber operand = new BytesNumber(-1, -1);
  550. for (int dataPos = 0; dataPos < data.length; dataPos++) {
  551. int b0 = data[dataPos] & 0xff;
  552. if (b0 == 10 && hasLocalSubroutines) {
  553. int subrNumber = getSubrNumber(localIndexSubr.getNumObjects(), operand.getNumber());
  554. int newRef = getNewRefForReference(subrNumber, localUniques, localIndexSubr, subsetLocalIndexSubr,
  555. subsetLocalSubrCount);
  556. if (newRef != -1) {
  557. byte[] newData = constructNewRefData(dataPos, data, operand, subsetLocalSubrCount,
  558. newRef, new int[] {10});
  559. dataPos -= data.length - newData.length;
  560. data = newData;
  561. }
  562. operand.clearNumber();
  563. } else if (b0 == 29 && hasGlobalSubroutines) {
  564. int subrNumber = getSubrNumber(globalIndexSubr.getNumObjects(), operand.getNumber());
  565. int newRef = getNewRefForReference(subrNumber, globalUniques, globalIndexSubr, subsetGlobalIndexSubr,
  566. subsetGlobalSubrCount);
  567. if (newRef != -1) {
  568. byte[] newData = constructNewRefData(dataPos, data, operand, subsetGlobalSubrCount,
  569. newRef, new int[] {29});
  570. dataPos -= (data.length - newData.length);
  571. data = newData;
  572. }
  573. operand.clearNumber();
  574. } else if ((b0 >= 0 && b0 <= 27) || (b0 >= 29 && b0 <= 31)) {
  575. operand.clearNumber();
  576. if (b0 == 19 || b0 == 20) {
  577. dataPos += 1;
  578. }
  579. } else if (b0 == 28 || (b0 >= 32 && b0 <= 255)) {
  580. operand = readNumber(b0, data, dataPos);
  581. dataPos += operand.getNumBytes() - 1;
  582. }
  583. }
  584. //Return the data with the modified references to our arrays
  585. return data;
  586. }
  587. private int getNewRefForReference(int subrNumber, List<Integer> uniquesArray,
  588. CFFIndexData indexSubr, List<byte[]> subsetIndexSubr, int subrCount) throws IOException {
  589. int newRef = -1;
  590. if (!uniquesArray.contains(subrNumber)) {
  591. if (subrNumber < indexSubr.getNumObjects()) {
  592. byte[] subr = indexSubr.getValue(subrNumber);
  593. subr = readCharStringData(subr, subrCount);
  594. if (!uniquesArray.contains(subrNumber)) {
  595. uniquesArray.add(subrNumber);
  596. subsetIndexSubr.add(subr);
  597. newRef = subsetIndexSubr.size() - 1;
  598. } else {
  599. newRef = uniquesArray.indexOf(subrNumber);
  600. }
  601. }
  602. } else {
  603. newRef = uniquesArray.indexOf(subrNumber);
  604. }
  605. return newRef;
  606. }
  607. private int getBias(int subrCount) {
  608. if (subrCount < 1240) {
  609. return 107;
  610. } else if (subrCount < 33900) {
  611. return 1131;
  612. } else {
  613. return 32768;
  614. }
  615. }
  616. private byte[] constructNewRefData(int curDataPos, byte[] currentData, BytesNumber operand,
  617. int fullSubsetIndexSize, int curSubsetIndexSize, int[] operatorCode) {
  618. //Create the new array with the modified reference
  619. byte[] newData;
  620. int startRef = curDataPos - operand.getNumBytes();
  621. int length = operand.getNumBytes() + 1;
  622. byte[] preBytes = new byte[startRef];
  623. System.arraycopy(currentData, 0, preBytes, 0, startRef);
  624. int newBias = getBias(fullSubsetIndexSize);
  625. int newRef = curSubsetIndexSize - newBias;
  626. byte[] newRefBytes = createNewRef(newRef, operatorCode, -1);
  627. newData = concatArray(preBytes, newRefBytes);
  628. byte[] postBytes = new byte[currentData.length - (startRef + length)];
  629. System.arraycopy(currentData, startRef + length, postBytes, 0,
  630. currentData.length - (startRef + length));
  631. return concatArray(newData, postBytes);
  632. }
  633. public static byte[] createNewRef(int newRef, int[] operatorCode, int forceLength) {
  634. byte[] newRefBytes;
  635. int sizeOfOperator = operatorCode.length;
  636. if ((forceLength == -1 && newRef <= 107) || forceLength == 1) {
  637. newRefBytes = new byte[1 + sizeOfOperator];
  638. //The index values are 0 indexed
  639. newRefBytes[0] = (byte)(newRef + 139);
  640. for (int i = 0; i < operatorCode.length; i++) {
  641. newRefBytes[1 + i] = (byte)operatorCode[i];
  642. }
  643. } else if ((forceLength == -1 && newRef <= 1131) || forceLength == 2) {
  644. newRefBytes = new byte[2 + sizeOfOperator];
  645. if (newRef <= 363) {
  646. newRefBytes[0] = (byte)247;
  647. } else if (newRef <= 619) {
  648. newRefBytes[0] = (byte)248;
  649. } else if (newRef <= 875) {
  650. newRefBytes[0] = (byte)249;
  651. } else {
  652. newRefBytes[0] = (byte)250;
  653. }
  654. newRefBytes[1] = (byte)(newRef - 108);
  655. for (int i = 0; i < operatorCode.length; i++) {
  656. newRefBytes[2 + i] = (byte)operatorCode[i];
  657. }
  658. } else if ((forceLength == -1 && newRef <= 32767) || forceLength == 3) {
  659. newRefBytes = new byte[3 + sizeOfOperator];
  660. newRefBytes[0] = 28;
  661. newRefBytes[1] = (byte)(newRef >> 8);
  662. newRefBytes[2] = (byte)newRef;
  663. for (int i = 0; i < operatorCode.length; i++) {
  664. newRefBytes[3 + i] = (byte)operatorCode[i];
  665. }
  666. } else {
  667. newRefBytes = new byte[5 + sizeOfOperator];
  668. newRefBytes[0] = 29;
  669. newRefBytes[1] = (byte)(newRef >> 24);
  670. newRefBytes[2] = (byte)(newRef >> 16);
  671. newRefBytes[3] = (byte)(newRef >> 8);
  672. newRefBytes[4] = (byte)newRef;
  673. for (int i = 0; i < operatorCode.length; i++) {
  674. newRefBytes[5 + i] = (byte)operatorCode[i];
  675. }
  676. }
  677. return newRefBytes;
  678. }
  679. public static byte[] concatArray(byte[] a, byte[] b) {
  680. int aLen = a.length;
  681. int bLen = b.length;
  682. byte[] c = new byte[aLen + bLen];
  683. System.arraycopy(a, 0, c, 0, aLen);
  684. System.arraycopy(b, 0, c, aLen, bLen);
  685. return c;
  686. }
  687. private int writeIndex(List<byte[]> dataArray) {
  688. int hdrTotal = 3;
  689. //2 byte number of items
  690. this.writeCard16(dataArray.size());
  691. //Offset Size: 1 byte = 256, 2 bytes = 65536 etc.
  692. int totLength = 0;
  693. for (int i = 0; i < dataArray.size(); i++) {
  694. totLength += dataArray.get(i).length;
  695. }
  696. int offSize = 1;
  697. if (totLength <= (1 << 8)) {
  698. offSize = 1;
  699. } else if (totLength <= (1 << 16)) {
  700. offSize = 2;
  701. } else if (totLength <= (1 << 24)) {
  702. offSize = 3;
  703. } else {
  704. offSize = 4;
  705. }
  706. this.writeByte(offSize);
  707. //Count the first offset 1
  708. hdrTotal += offSize;
  709. int total = 0;
  710. for (int i = 0; i < dataArray.size(); i++) {
  711. hdrTotal += offSize;
  712. int length = dataArray.get(i).length;
  713. switch (offSize) {
  714. case 1:
  715. if (i == 0) {
  716. writeByte(1);
  717. }
  718. total += length;
  719. writeByte(total + 1);
  720. break;
  721. case 2:
  722. if (i == 0) {
  723. writeCard16(1);
  724. }
  725. total += length;
  726. writeCard16(total + 1);
  727. break;
  728. case 3:
  729. if (i == 0) {
  730. writeThreeByteNumber(1);
  731. }
  732. total += length;
  733. writeThreeByteNumber(total + 1);
  734. break;
  735. case 4:
  736. if (i == 0) {
  737. writeULong(1);
  738. }
  739. total += length;
  740. writeULong(total + 1);
  741. break;
  742. default:
  743. throw new AssertionError("Offset Size was not an expected value.");
  744. }
  745. }
  746. for (int i = 0; i < dataArray.size(); i++) {
  747. writeBytes(dataArray.get(i));
  748. }
  749. return hdrTotal + total;
  750. }
  751. private BytesNumber readNumber(int b0, byte[] input, int curPos) throws IOException {
  752. if (b0 == 28) {
  753. int b1 = input[curPos + 1] & 0xff;
  754. int b2 = input[curPos + 2] & 0xff;
  755. return new BytesNumber(Integer.valueOf((short) (b1 << 8 | b2)), 3);
  756. } else if (b0 >= 32 && b0 <= 246) {
  757. return new BytesNumber(Integer.valueOf(b0 - 139), 1);
  758. } else if (b0 >= 247 && b0 <= 250) {
  759. int b1 = input[curPos + 1] & 0xff;
  760. return new BytesNumber(Integer.valueOf((b0 - 247) * 256 + b1 + 108), 2);
  761. } else if (b0 >= 251 && b0 <= 254) {
  762. int b1 = input[curPos + 1] & 0xff;
  763. return new BytesNumber(Integer.valueOf(-(b0 - 251) * 256 - b1 - 108), 2);
  764. } else if (b0 == 255) {
  765. int b1 = input[curPos + 1] & 0xff;
  766. int b2 = input[curPos + 2] & 0xff;
  767. return new BytesNumber(Integer.valueOf((short)(b1 << 8 | b2)), 5);
  768. } else {
  769. throw new IllegalArgumentException();
  770. }
  771. }
  772. /**
  773. * A class used to store the last number operand and also it's size in bytes
  774. */
  775. private static final class BytesNumber {
  776. private int number;
  777. private int numBytes;
  778. public BytesNumber(int number, int numBytes) {
  779. this.number = number;
  780. this.numBytes = numBytes;
  781. }
  782. public int getNumber() {
  783. return this.number;
  784. }
  785. public int getNumBytes() {
  786. return this.numBytes;
  787. }
  788. public void clearNumber() {
  789. this.number = -1;
  790. this.numBytes = -1;
  791. }
  792. }
  793. private void writeCharsetTable(boolean cidFont) throws IOException {
  794. writeByte(0);
  795. for (int gid : gidToSID.keySet()) {
  796. if (cidFont && gid == 0) {
  797. continue;
  798. }
  799. writeCard16((cidFont) ? gid : gidToSID.get(gid));
  800. }
  801. }
  802. private void writePrivateDict() throws IOException {
  803. Map<String, DICTEntry> topDICT = cffReader.getTopDictEntries();
  804. DICTEntry privateEntry = topDICT.get("Private");
  805. if (privateEntry != null) {
  806. writeBytes(cffReader.getPrivateDictBytes(privateEntry));
  807. }
  808. }
  809. private void updateOffsets(int topDictOffset, int charsetOffset, int charStringOffset,
  810. int privateDictOffset, int localIndexOffset, int encodingOffset)
  811. throws IOException {
  812. Map<String, DICTEntry> topDICT = cffReader.getTopDictEntries();
  813. Map<String, DICTEntry> privateDICT = null;
  814. DICTEntry privateEntry = topDICT.get("Private");
  815. if (privateEntry != null) {
  816. privateDICT = cffReader.getPrivateDict(privateEntry);
  817. }
  818. int dataPos = 3 + (cffReader.getTopDictIndex().getOffSize()
  819. * cffReader.getTopDictIndex().getOffsets().length);
  820. int dataTopDictOffset = topDictOffset + dataPos;
  821. updateFixedOffsets(topDICT, dataTopDictOffset, charsetOffset, charStringOffset, encodingOffset);
  822. if (privateDICT != null) {
  823. //Private index offset in the top dict
  824. int oldPrivateOffset = dataTopDictOffset + privateEntry.getOffset();
  825. output = updateOffset(output, oldPrivateOffset + privateEntry.getOperandLengths().get(0),
  826. privateEntry.getOperandLengths().get(1), privateDictOffset);
  827. //Update the local subroutine index offset in the private dict
  828. DICTEntry subroutines = privateDICT.get("Subrs");
  829. int oldLocalSubrOffset = privateDictOffset + subroutines.getOffset();
  830. //Value needs to be converted to -139 etc.
  831. int encodeValue = 0;
  832. if (subroutines.getOperandLength() == 1) {
  833. encodeValue = 139;
  834. }
  835. output = updateOffset(output, oldLocalSubrOffset, subroutines.getOperandLength(),
  836. (localIndexOffset - privateDictOffset) + encodeValue);
  837. }
  838. }
  839. private void updateFixedOffsets(Map<String, DICTEntry> topDICT, int dataTopDictOffset,
  840. int charsetOffset, int charStringOffset, int encodingOffset) {
  841. //Charset offset in the top dict
  842. DICTEntry charset = topDICT.get("charset");
  843. int oldCharsetOffset = dataTopDictOffset + charset.getOffset();
  844. output = updateOffset(output, oldCharsetOffset, charset.getOperandLength(), charsetOffset);
  845. //Char string index offset in the private dict
  846. DICTEntry charString = topDICT.get("CharStrings");
  847. int oldCharStringOffset = dataTopDictOffset + charString.getOffset();
  848. output = updateOffset(output, oldCharStringOffset, charString.getOperandLength(), charStringOffset);
  849. DICTEntry encodingEntry = topDICT.get("Encoding");
  850. if (encodingEntry != null && encodingEntry.getOperands().get(0).intValue() != 0
  851. && encodingEntry.getOperands().get(0).intValue() != 1) {
  852. int oldEncodingOffset = dataTopDictOffset + encodingEntry.getOffset();
  853. output = updateOffset(output, oldEncodingOffset, encodingEntry.getOperandLength(), encodingOffset);
  854. }
  855. }
  856. private void updateCIDOffsets(int topDictDataOffset, int fdArrayOffset, int fdSelectOffset,
  857. int charsetOffset, int charStringOffset, int encodingOffset) {
  858. LinkedHashMap<String, DICTEntry> topDict = cffReader.getTopDictEntries();
  859. DICTEntry fdArrayEntry = topDict.get("FDArray");
  860. if (fdArrayEntry != null) {
  861. output = updateOffset(output, topDictDataOffset + fdArrayEntry.getOffset() - 1,
  862. fdArrayEntry.getOperandLength(), fdArrayOffset);
  863. }
  864. DICTEntry fdSelect = topDict.get("FDSelect");
  865. if (fdSelect != null) {
  866. output = updateOffset(output, topDictDataOffset + fdSelect.getOffset() - 1,
  867. fdSelect.getOperandLength(), fdSelectOffset);
  868. }
  869. updateFixedOffsets(topDict, topDictDataOffset, charsetOffset, charStringOffset, encodingOffset);
  870. }
  871. private byte[] updateOffset(byte[] out, int position, int length, int replacement) {
  872. switch (length) {
  873. case 1:
  874. out[position] = (byte)(replacement & 0xFF);
  875. break;
  876. case 2:
  877. if (replacement <= 363) {
  878. out[position] = (byte)247;
  879. } else if (replacement <= 619) {
  880. out[position] = (byte)248;
  881. } else if (replacement <= 875) {
  882. out[position] = (byte)249;
  883. } else {
  884. out[position] = (byte)250;
  885. }
  886. out[position + 1] = (byte)(replacement - 108);
  887. break;
  888. case 3:
  889. out[position] = (byte)28;
  890. out[position + 1] = (byte)((replacement >> 8) & 0xFF);
  891. out[position + 2] = (byte)(replacement & 0xFF);
  892. break;
  893. case 5:
  894. out[position] = (byte)29;
  895. out[position + 1] = (byte)((replacement >> 24) & 0xFF);
  896. out[position + 2] = (byte)((replacement >> 16) & 0xFF);
  897. out[position + 3] = (byte)((replacement >> 8) & 0xFF);
  898. out[position + 4] = (byte)(replacement & 0xFF);
  899. break;
  900. default:
  901. }
  902. return out;
  903. }
  904. /**
  905. * Appends a byte to the output array,
  906. * updates currentPost but not realSize
  907. */
  908. private void writeByte(int b) {
  909. output[currentPos++] = (byte)b;
  910. realSize++;
  911. }
  912. /**
  913. * Appends a USHORT to the output array,
  914. * updates currentPost but not realSize
  915. */
  916. private void writeCard16(int s) {
  917. byte b1 = (byte)((s >> 8) & 0xff);
  918. byte b2 = (byte)(s & 0xff);
  919. writeByte(b1);
  920. writeByte(b2);
  921. }
  922. private void writeThreeByteNumber(int s) {
  923. byte b1 = (byte)((s >> 16) & 0xFF);
  924. byte b2 = (byte)((s >> 8) & 0xFF);
  925. byte b3 = (byte)(s & 0xFF);
  926. output[currentPos++] = b1;
  927. output[currentPos++] = b2;
  928. output[currentPos++] = b3;
  929. realSize += 3;
  930. }
  931. /**
  932. * Appends a ULONG to the output array,
  933. * at the given position
  934. */
  935. private void writeULong(int s) {
  936. byte b1 = (byte)((s >> 24) & 0xff);
  937. byte b2 = (byte)((s >> 16) & 0xff);
  938. byte b3 = (byte)((s >> 8) & 0xff);
  939. byte b4 = (byte)(s & 0xff);
  940. output[currentPos++] = b1;
  941. output[currentPos++] = b2;
  942. output[currentPos++] = b3;
  943. output[currentPos++] = b4;
  944. realSize += 4;
  945. }
  946. /**
  947. * Returns a subset of the fonts (readFont() MUST be called first in order to create the
  948. * subset).
  949. * @return byte array
  950. */
  951. public byte[] getFontSubset() {
  952. byte[] ret = new byte[realSize];
  953. System.arraycopy(output, 0, ret, 0, realSize);
  954. return ret;
  955. }
  956. }