1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 /* $Id$ */
19 
20 package org.apache.fop.fonts.truetype;
21 
22 import java.io.ByteArrayInputStream;
23 import java.io.ByteArrayOutputStream;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.util.ArrayList;
27 import java.util.Arrays;
28 import java.util.Collections;
29 import java.util.Comparator;
30 import java.util.HashMap;
31 import java.util.LinkedHashMap;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Map.Entry;
35 
36 import org.apache.commons.logging.Log;
37 import org.apache.commons.logging.LogFactory;
38 
39 import org.apache.fontbox.cff.CFFStandardString;
40 import org.apache.fontbox.cff.CFFType1Font;
41 import org.apache.fontbox.cff.CharStringCommand;
42 import org.apache.fontbox.cff.Type2CharString;
43 
44 import org.apache.fop.fonts.MultiByteFont;
45 import org.apache.fop.fonts.cff.CFFDataReader;
46 import org.apache.fop.fonts.cff.CFFDataReader.CFFIndexData;
47 import org.apache.fop.fonts.cff.CFFDataReader.DICTEntry;
48 import org.apache.fop.fonts.cff.CFFDataReader.FDSelect;
49 import org.apache.fop.fonts.cff.CFFDataReader.FontDict;
50 import org.apache.fop.fonts.cff.CFFDataReader.Format0FDSelect;
51 import org.apache.fop.fonts.cff.CFFDataReader.Format3FDSelect;
52 import org.apache.fop.fonts.type1.AdobeStandardEncoding;
53 
54 /**
55  * Reads an OpenType CFF file and generates a subset
56  * The OpenType specification can be found at the Microsoft
57  * Typography site: http://www.microsoft.com/typography/otspec/
58  */
59 public class OTFSubSetFile extends OTFSubSetWriter {
60 
61     /** A map containing each glyph to be included in the subset
62       * with their existing and new GID's **/
63     protected Map<Integer, Integer> subsetGlyphs = new LinkedHashMap<Integer, Integer>();
64 
65     /** A map of the new GID to SID used to construct the charset table **/
66     protected Map<Integer, Integer> gidToSID;
67 
68     protected CFFIndexData localIndexSubr;
69     protected CFFIndexData globalIndexSubr;
70 
71     /** List of subroutines to write to the local / global indexes in the subset font **/
72     protected List<byte[]> subsetLocalIndexSubr;
73     protected List<byte[]> subsetGlobalIndexSubr;
74 
75     /** For fonts which have an FDSelect or ROS flag in Top Dict, this is used to store the
76      * local subroutine indexes for each group as opposed to the above subsetLocalIndexSubr */
77     protected List<List<byte[]>> fdSubrs;
78 
79     /** The subset FD Select table used to store the mappings between glyphs and their
80      * associated FDFont object which point to a private dict and local subroutines. */
81     private Map<Integer, FDIndexReference> subsetFDSelect;
82 
83     /** A list of unique subroutines from the global / local subroutine indexes */
84     protected List<Integer> localUniques;
85     protected List<Integer> globalUniques;
86 
87     /** A store of the number of subroutines each global / local subroutine will store **/
88     protected int subsetLocalSubrCount;
89     protected int subsetGlobalSubrCount;
90 
91     /** A list of char string data for each glyph to be stored in the subset font **/
92     protected List<byte[]> subsetCharStringsIndex;
93 
94     /** The embedded name to change in the name table **/
95     protected String embeddedName;
96 
97     /** An array used to hold the string index data for the subset font **/
98     protected List<byte[]> stringIndexData = new ArrayList<byte[]>();
99 
100     /** The CFF reader object used to read data and offsets from the original font file */
101     protected CFFDataReader cffReader;
102 
103     /** The class used to represent this font **/
104     private MultiByteFont mbFont;
105 
106     /** The number of standard strings in CFF **/
107     public static final int NUM_STANDARD_STRINGS = 391;
108     /** The operator used to identify a local subroutine reference */
109     private static final int LOCAL_SUBROUTINE = 10;
110     /** The operator used to identify a global subroutine reference */
111     private static final int GLOBAL_SUBROUTINE = 29;
112 
113     private static final String ACCENT_CMD = "seac";
114 
115     /** The parser used to parse type2 charstring */
116     private Type2Parser type2Parser;
117 
OTFSubSetFile()118     public OTFSubSetFile() throws IOException {
119         super();
120     }
121 
readFont(FontFileReader in, String embeddedName, MultiByteFont mbFont)122     public void readFont(FontFileReader in, String embeddedName, MultiByteFont mbFont) throws IOException {
123         readFont(in, embeddedName, mbFont, mbFont.getUsedGlyphs());
124     }
125 
126     /**
127      * Reads and creates a subset of the font.
128      *
129      * @param in FontFileReader to read from
130      * @param embeddedName Name to be checked for in the font file
131      * @param usedGlyphs Map of glyphs (glyphs has old index as (Integer) key and
132      * new index as (Integer) value)
133      * @throws IOException in case of an I/O problem
134      */
readFont(FontFileReader in, String embeddedName, MultiByteFont mbFont, Map<Integer, Integer> usedGlyphs)135     void readFont(FontFileReader in, String embeddedName, MultiByteFont mbFont,
136             Map<Integer, Integer> usedGlyphs) throws IOException {
137         this.mbFont = mbFont;
138         fontFile = in;
139         this.embeddedName = embeddedName;
140         initializeFont(in);
141         cffReader = new CFFDataReader(fontFile);
142         mapChars(usedGlyphs);
143         //Sort by the new GID and store in a LinkedHashMap
144         subsetGlyphs = sortByValue(usedGlyphs);
145         //Create the CIDFontType0C data
146         createCFF();
147     }
148 
mapChars(Map<Integer, Integer> usedGlyphs)149     private void mapChars(Map<Integer, Integer> usedGlyphs) throws IOException {
150         if (fileFont instanceof CFFType1Font) {
151             CFFType1Font cffType1Font = (CFFType1Font) fileFont;
152             subsetGlyphs = sortByValue(usedGlyphs);
153             for (int gid : subsetGlyphs.keySet()) {
154                 Type2CharString type2CharString = cffType1Font.getType2CharString(gid);
155                 List<Number> stack = new ArrayList<Number>();
156                 for (Object obj : type2CharString.getType1Sequence()) {
157                     if (obj instanceof CharStringCommand) {
158                         String name = CharStringCommand.TYPE1_VOCABULARY.get(((CharStringCommand) obj).getKey());
159                         if (ACCENT_CMD.equals(name)) {
160                             int first = stack.get(3).intValue();
161                             int second = stack.get(4).intValue();
162                             mbFont.mapChar(AdobeStandardEncoding.getUnicodeFromCodePoint(first));
163                             mbFont.mapChar(AdobeStandardEncoding.getUnicodeFromCodePoint(second));
164                         }
165                         stack.clear();
166                     } else {
167                         stack.add((Number) obj);
168                     }
169                 }
170             }
171         }
172     }
173 
sortByValue(Map<Integer, Integer> map)174     private Map<Integer, Integer> sortByValue(Map<Integer, Integer> map) {
175         List<Entry<Integer, Integer>> list = new ArrayList<Entry<Integer, Integer>>(map.entrySet());
176         Collections.sort(list, new Comparator<Entry<Integer, Integer>>() {
177              public int compare(Entry<Integer, Integer> o1, Entry<Integer, Integer> o2) {
178                   return ((Comparable<Integer>) o1.getValue()).compareTo(o2.getValue());
179              }
180         });
181 
182        Map<Integer, Integer> result = new LinkedHashMap<Integer, Integer>();
183        for (Entry<Integer, Integer> entry : list) {
184            result.put(entry.getKey(), entry.getValue());
185        }
186        return result;
187     }
188 
createCFF()189     protected void createCFF() throws IOException {
190         //Header
191         writeBytes(cffReader.getHeader());
192 
193         //Name Index
194         writeIndex(Arrays.asList(embedFontName.getBytes("UTF-8")));
195 
196         Offsets offsets = new Offsets();
197 
198         //Top DICT Index and Data
199         offsets.topDictData = currentPos + writeTopDICT();
200 
201         boolean hasFDSelect = cffReader.getFDSelect() != null;
202 
203         //Create the char string index data and related local / global subroutines
204         if (hasFDSelect) {
205             createCharStringDataCID();
206         } else {
207             createCharStringData();
208         }
209 
210         //If it is a CID-Keyed font, store each FD font and add each SID
211         List<Integer> fontNameSIDs = null;
212         List<Integer> subsetFDFonts = null;
213         if (hasFDSelect) {
214             subsetFDFonts = getUsedFDFonts();
215             fontNameSIDs = storeFDStrings(subsetFDFonts);
216         }
217 
218         //String index
219         writeStringIndex();
220 
221         //Global subroutine index
222         writeIndex(subsetGlobalIndexSubr);
223 
224         //Encoding
225         offsets.encoding = currentPos;
226 
227         //Charset table
228         offsets.charset = currentPos;
229         writeCharsetTable(hasFDSelect);
230 
231         //FDSelect table
232         offsets.fdSelect = currentPos;
233         if (hasFDSelect) {
234             writeFDSelect();
235             if (!isCharStringBeforeFD()) {
236                 offsets.fdArray = writeFDArray(subsetFDFonts, fontNameSIDs);
237             }
238         }
239 
240         //Char Strings Index
241         offsets.charString = currentPos;
242         writeIndex(subsetCharStringsIndex);
243         if (hasFDSelect) {
244             if (isCharStringBeforeFD()) {
245                 offsets.fdArray = writeFDArray(subsetFDFonts, fontNameSIDs);
246             }
247             updateCIDOffsets(offsets);
248         } else {
249             //Keep offset to modify later with the local subroutine index offset
250             offsets.privateDict = currentPos;
251             writePrivateDict();
252 
253             //Local subroutine index
254             offsets.localIndex = currentPos;
255             writeIndex(subsetLocalIndexSubr);
256 
257             //Update the offsets
258             updateOffsets(offsets);
259         }
260     }
261 
262     static class Offsets {
263         Integer topDictData;
264         Integer encoding;
265         Integer charset;
266         Integer fdSelect;
267         Integer charString;
268         Integer fdArray;
269         Integer privateDict;
270         Integer localIndex;
271     }
272 
writeFDArray(List<Integer> subsetFDFonts, List<Integer> fontNameSIDs)273     private int writeFDArray(List<Integer> subsetFDFonts, List<Integer> fontNameSIDs) throws IOException {
274         List<Integer> privateDictOffsets = writeCIDDictsAndSubrs(subsetFDFonts);
275         return writeFDArray(subsetFDFonts, privateDictOffsets, fontNameSIDs);
276     }
277 
isCharStringBeforeFD()278     private boolean isCharStringBeforeFD() {
279         LinkedHashMap<String, DICTEntry> entries = cffReader.getTopDictEntries();
280         int len = entries.get("CharStrings").getOperandLength();
281         if (entries.containsKey("FDArray")) {
282             int len2 = entries.get("FDArray").getOperandLength();
283             return len < len2;
284         }
285         return true;
286     }
287 
storeFDStrings(List<Integer> uniqueNewRefs)288     protected List<Integer> storeFDStrings(List<Integer> uniqueNewRefs) throws IOException {
289         List<Integer> fontNameSIDs = new ArrayList<Integer>();
290         List<FontDict> fdFonts = cffReader.getFDFonts();
291         for (int uniqueNewRef : uniqueNewRefs) {
292             FontDict fdFont = fdFonts.get(uniqueNewRef);
293             byte[] fdFontByteData = fdFont.getByteData();
294             Map<String, DICTEntry> fdFontDict = cffReader.parseDictData(fdFontByteData);
295             fontNameSIDs.add(stringIndexData.size() + NUM_STANDARD_STRINGS);
296             stringIndexData.add(cffReader.getStringIndex().getValue(fdFontDict.get("FontName")
297                     .getOperands().get(0).intValue() - NUM_STANDARD_STRINGS));
298         }
299         return fontNameSIDs;
300     }
301 
writeTopDICT()302     protected int writeTopDICT() throws IOException {
303         Map<String, DICTEntry> topDICT = cffReader.getTopDictEntries();
304         List<String> topDictStringEntries = Arrays.asList("version", "Notice", "Copyright",
305                 "FullName", "FamilyName", "Weight", "PostScript");
306         ByteArrayOutputStream dict = new ByteArrayOutputStream();
307         int offsetExtra = 0;
308         for (Map.Entry<String, DICTEntry> dictEntry : topDICT.entrySet()) {
309             String dictKey = dictEntry.getKey();
310             DICTEntry entry = dictEntry.getValue();
311             //If the value is an SID, update the reference but keep the size the same
312             entry.setOffset(entry.getOffset() + offsetExtra);
313             if (dictKey.equals("CharStrings") && entry.getOperandLength() < 5) {
314                 byte[] extra = new byte[5 - entry.getOperandLength()];
315                 offsetExtra += extra.length;
316                 dict.write(extra);
317                 dict.write(entry.getByteData());
318                 entry.setOperandLength(5);
319             } else if (dictKey.equals("ROS")) {
320                 dict.write(writeROSEntry(entry));
321             } else if (dictKey.equals("CIDCount")) {
322                 dict.write(writeCIDCount(entry));
323             } else if (topDictStringEntries.contains(dictKey)) {
324                 if (entry.getOperandLength() < 2) {
325                     entry.setOperandLength(2);
326                     offsetExtra++;
327                 }
328                 dict.write(writeTopDictStringEntry(entry));
329             } else {
330                 dict.write(entry.getByteData());
331             }
332         }
333         byte[] topDictIndex = cffReader.getTopDictIndex().getByteData();
334         int offSize = topDictIndex[2];
335         return writeIndex(Arrays.asList(dict.toByteArray()), offSize) - dict.size();
336     }
337 
writeROSEntry(DICTEntry dictEntry)338     private byte[] writeROSEntry(DICTEntry dictEntry) throws IOException {
339         int sidA = dictEntry.getOperands().get(0).intValue();
340         if (sidA > 390) {
341             stringIndexData.add(cffReader.getStringIndex().getValue(sidA - NUM_STANDARD_STRINGS));
342         }
343         int sidAStringIndex = stringIndexData.size() + 390;
344         int sidB = dictEntry.getOperands().get(1).intValue();
345         if (sidB > 390) {
346             stringIndexData.add("Identity".getBytes("UTF-8"));
347         }
348         int sidBStringIndex = stringIndexData.size() + 390;
349         byte[] cidEntryByteData = dictEntry.getByteData();
350         updateOffset(cidEntryByteData, 0, dictEntry.getOperandLengths().get(0),
351                 sidAStringIndex);
352         updateOffset(cidEntryByteData, dictEntry.getOperandLengths().get(0),
353                 dictEntry.getOperandLengths().get(1), sidBStringIndex);
354         updateOffset(cidEntryByteData, dictEntry.getOperandLengths().get(0)
355                 + dictEntry.getOperandLengths().get(1), dictEntry.getOperandLengths().get(2), 0);
356         return cidEntryByteData;
357     }
358 
writeCIDCount(DICTEntry dictEntry)359     protected byte[] writeCIDCount(DICTEntry dictEntry) throws IOException {
360         byte[] cidCountByteData = dictEntry.getByteData();
361         updateOffset(cidCountByteData, 0, dictEntry.getOperandLengths().get(0),
362                 subsetGlyphs.size());
363         return cidCountByteData;
364     }
365 
writeTopDictStringEntry(DICTEntry dictEntry)366     private byte[] writeTopDictStringEntry(DICTEntry dictEntry) throws IOException {
367         int sid = dictEntry.getOperands().get(0).intValue();
368         if (sid > 391) {
369             stringIndexData.add(cffReader.getStringIndex().getValue(sid - 391));
370         }
371         byte[] newDictEntry = createNewRef(stringIndexData.size() + 390, dictEntry.getOperator(),
372                 dictEntry.getOperandLength(), true);
373         return newDictEntry;
374     }
375 
writeStringIndex()376     private void writeStringIndex() throws IOException {
377         Map<String, DICTEntry> topDICT = cffReader.getTopDictEntries();
378         int charsetOffset = topDICT.get("charset").getOperands().get(0).intValue();
379 
380         gidToSID = new LinkedHashMap<Integer, Integer>();
381 
382         for (Entry<Integer, Integer> subsetGlyph : subsetGlyphs.entrySet()) {
383             int gid = subsetGlyph.getKey();
384             int v = subsetGlyph.getValue();
385             int sid = cffReader.getSIDFromGID(charsetOffset, gid);
386             //Check whether the SID falls into the standard string set
387             if (sid < NUM_STANDARD_STRINGS) {
388                 gidToSID.put(v, sid);
389                 if (mbFont != null) {
390                     mbFont.mapUsedGlyphName(v, CFFStandardString.getName(sid));
391                 }
392             } else {
393                 int index = sid - NUM_STANDARD_STRINGS;
394                 //index is 0 based, should use < not <=
395                 if (index < cffReader.getStringIndex().getNumObjects()) {
396                     byte[] value = cffReader.getStringIndex().getValue(index);
397                     if (mbFont != null) {
398                         mbFont.mapUsedGlyphName(v, new String(value, "UTF-8"));
399                     }
400                     gidToSID.put(v, stringIndexData.size() + 391);
401                     stringIndexData.add(value);
402                 } else {
403                     if (mbFont != null) {
404                         mbFont.mapUsedGlyphName(v, ".notdef");
405                     }
406                     gidToSID.put(v, index);
407                 }
408             }
409         }
410         //Write the String Index
411         writeIndex(stringIndexData);
412     }
413 
createCharStringDataCID()414     protected void createCharStringDataCID() throws IOException {
415         CFFIndexData charStringsIndex = cffReader.getCharStringIndex();
416 
417         FDSelect fontDictionary = cffReader.getFDSelect();
418         if (fontDictionary instanceof Format0FDSelect) {
419             throw new UnsupportedOperationException("OTF CFF CID Format0 currently not implemented");
420         } else if (fontDictionary instanceof Format3FDSelect) {
421             Format3FDSelect fdSelect = (Format3FDSelect)fontDictionary;
422             Map<Integer, Integer> subsetGroups = new HashMap<Integer, Integer>();
423 
424             List<Integer> uniqueGroups = new ArrayList<Integer>();
425             Map<Integer, Integer> rangeMap = fdSelect.getRanges();
426             Integer[] ranges = rangeMap.keySet().toArray(new Integer[rangeMap.size()]);
427             for (int gid : subsetGlyphs.keySet()) {
428                 int i = 0;
429                 for (Entry<Integer, Integer> entry : rangeMap.entrySet()) {
430                     int nextRange;
431                     if (i < ranges.length - 1) {
432                         nextRange = ranges[i + 1];
433                     } else {
434                         nextRange = fdSelect.getSentinelGID();
435                     }
436                     if (gid >= entry.getKey() && gid < nextRange) {
437                         int r = entry.getValue();
438                         subsetGroups.put(gid, r);
439                         if (!uniqueGroups.contains(r)) {
440                             uniqueGroups.add(r);
441                         }
442                     }
443                     i++;
444                 }
445             }
446 
447             //Prepare resources
448             globalIndexSubr = cffReader.getGlobalIndexSubr();
449 
450             //Create the new char string index
451             subsetCharStringsIndex = new ArrayList<byte[]>();
452 
453             globalUniques = new ArrayList<Integer>();
454 
455             subsetFDSelect = new LinkedHashMap<Integer, FDIndexReference>();
456 
457             List<List<Integer>> foundLocalUniques = new ArrayList<List<Integer>>();
458             for (int u : uniqueGroups) {
459                 foundLocalUniques.add(new ArrayList<Integer>());
460             }
461             Map<Integer, Integer> gidHintMaskLengths = new HashMap<Integer, Integer>();
462             for (Entry<Integer, Integer> subsetGlyph : subsetGlyphs.entrySet()) {
463                 int gid = subsetGlyph.getKey();
464                 int group = subsetGroups.get(gid);
465                 localIndexSubr = cffReader.getFDFonts().get(group).getLocalSubrData();
466                 localUniques = foundLocalUniques.get(uniqueGroups.indexOf(group));
467                 type2Parser = new Type2Parser();
468 
469                 FDIndexReference newFDReference = new FDIndexReference(uniqueGroups.indexOf(group), group);
470                 subsetFDSelect.put(subsetGlyph.getValue(), newFDReference);
471                 byte[] data = charStringsIndex.getValue(gid);
472                 preScanForSubsetIndexSize(data);
473                 gidHintMaskLengths.put(gid, type2Parser.getMaskLength());
474             }
475 
476             //Create the two lists which are to store the local and global subroutines
477             subsetGlobalIndexSubr = new ArrayList<byte[]>();
478 
479             fdSubrs = new ArrayList<List<byte[]>>();
480             subsetGlobalSubrCount = globalUniques.size();
481             globalUniques.clear();
482             localUniques = null;
483 
484             for (List<Integer> foundLocalUnique : foundLocalUniques) {
485                 fdSubrs.add(new ArrayList<byte[]>());
486             }
487             List<List<Integer>> foundLocalUniquesB = new ArrayList<List<Integer>>();
488             for (int u : uniqueGroups) {
489                 foundLocalUniquesB.add(new ArrayList<Integer>());
490             }
491             for (Entry<Integer, Integer> subsetGlyph : subsetGlyphs.entrySet()) {
492                 int gid = subsetGlyph.getKey();
493                 int value = subsetGlyph.getValue();
494                 int group = subsetGroups.get(gid);
495                 localIndexSubr = cffReader.getFDFonts().get(group).getLocalSubrData();
496                 int newFDIndex = subsetFDSelect.get(value).getNewFDIndex();
497                 localUniques = foundLocalUniquesB.get(newFDIndex);
498                 byte[] data = charStringsIndex.getValue(gid);
499                 subsetLocalIndexSubr = fdSubrs.get(newFDIndex);
500                 subsetLocalSubrCount = foundLocalUniques.get(newFDIndex).size();
501                 type2Parser = new Type2Parser();
502                 type2Parser.setMaskLength(gidHintMaskLengths.get(gid));
503                 data = readCharStringData(data, subsetLocalSubrCount);
504                 subsetCharStringsIndex.add(data);
505             }
506         }
507     }
508 
writeFDSelect()509     protected void writeFDSelect() {
510         if (cffReader.getTopDictEntries().get("CharStrings").getOperandLength() == 2) {
511             Map<Integer, Integer> indexs = getFormat3Index();
512             writeByte(3); //Format
513             writeCard16(indexs.size());
514             int count = 0;
515             for (Entry<Integer, Integer> x : indexs.entrySet()) {
516                 writeCard16(count);
517                 writeByte(x.getKey());
518                 count += x.getValue();
519             }
520             writeCard16(subsetFDSelect.size());
521         } else {
522             writeByte(0); //Format
523             for (FDIndexReference e : subsetFDSelect.values()) {
524                 writeByte(e.getNewFDIndex());
525             }
526         }
527     }
528 
getFormat3Index()529     private Map<Integer, Integer> getFormat3Index() {
530         Map<Integer, Integer> indexs = new LinkedHashMap<Integer, Integer>();
531         int last = -1;
532         int count = 0;
533         for (FDIndexReference e : subsetFDSelect.values()) {
534             int i = e.getNewFDIndex();
535             count++;
536             if (i != last) {
537                 indexs.put(i, count);
538                 count = 1;
539             }
540             last = i;
541         }
542         indexs.put(last, count);
543         return indexs;
544     }
545 
getUsedFDFonts()546     protected List<Integer> getUsedFDFonts() {
547         List<Integer> uniqueNewRefs = new ArrayList<Integer>();
548         for (FDIndexReference e : subsetFDSelect.values()) {
549             int fdIndex = e.getOldFDIndex();
550             if (!uniqueNewRefs.contains(fdIndex)) {
551                 uniqueNewRefs.add(fdIndex);
552             }
553         }
554         return uniqueNewRefs;
555     }
556 
writeCIDDictsAndSubrs(List<Integer> uniqueNewRefs)557     protected List<Integer> writeCIDDictsAndSubrs(List<Integer> uniqueNewRefs)
558             throws IOException {
559         List<Integer> privateDictOffsets = new ArrayList<Integer>();
560         List<FontDict> fdFonts = cffReader.getFDFonts();
561         int i = 0;
562         for (int ref : uniqueNewRefs) {
563             FontDict curFDFont = fdFonts.get(ref);
564             byte[] fdPrivateDictByteData = curFDFont.getPrivateDictData();
565             Map<String, DICTEntry> fdPrivateDict = cffReader.parseDictData(fdPrivateDictByteData);
566             int privateDictOffset = currentPos;
567             privateDictOffsets.add(privateDictOffset);
568             DICTEntry subrs = fdPrivateDict.get("Subrs");
569             if (subrs != null) {
570                 fdPrivateDictByteData = resizeToFitOpLen(fdPrivateDictByteData, subrs);
571                 updateOffset(fdPrivateDictByteData, subrs.getOffset(),
572                         subrs.getOperandLength(),
573                         fdPrivateDictByteData.length);
574             }
575             writeBytes(fdPrivateDictByteData);
576             writeIndex(fdSubrs.get(i));
577             i++;
578         }
579         return privateDictOffsets;
580     }
581 
resizeToFitOpLen(byte[] fdPrivateDictByteData, DICTEntry subrs)582     private byte[] resizeToFitOpLen(byte[] fdPrivateDictByteData, DICTEntry subrs) throws IOException {
583         if (subrs.getOperandLength() == 2 && fdPrivateDictByteData.length < 108) {
584             ByteArrayOutputStream bos = new ByteArrayOutputStream();
585             bos.write(fdPrivateDictByteData);
586             bos.write(new byte[108 - fdPrivateDictByteData.length]);
587             fdPrivateDictByteData = bos.toByteArray();
588         }
589         return fdPrivateDictByteData;
590     }
591 
writeFDArray(List<Integer> uniqueNewRefs, List<Integer> privateDictOffsets, List<Integer> fontNameSIDs)592     protected int writeFDArray(List<Integer> uniqueNewRefs, List<Integer> privateDictOffsets,
593             List<Integer> fontNameSIDs)
594             throws IOException {
595         int offset = currentPos;
596         List<FontDict> fdFonts = cffReader.getFDFonts();
597         List<byte[]> index = new ArrayList<byte[]>();
598 
599         int i = 0;
600         for (int ref : uniqueNewRefs) {
601             FontDict fdFont = fdFonts.get(ref);
602             byte[] fdFontByteData = fdFont.getByteData();
603             Map<String, DICTEntry> fdFontDict = cffReader.parseDictData(fdFontByteData);
604             //Update the SID to the FontName
605             updateOffset(fdFontByteData, fdFontDict.get("FontName").getOffset() - 1,
606                     fdFontDict.get("FontName").getOperandLengths().get(0),
607                     fontNameSIDs.get(i));
608             //Update the Private dict reference
609             updateOffset(fdFontByteData, fdFontDict.get("Private").getOffset()
610                     + fdFontDict.get("Private").getOperandLengths().get(0),
611                     fdFontDict.get("Private").getOperandLengths().get(1),
612                     privateDictOffsets.get(i));
613             index.add(fdFontByteData);
614             i++;
615         }
616         writeIndex(index);
617         return offset;
618     }
619 
620     private static class FDIndexReference {
621         private int newFDIndex;
622         private int oldFDIndex;
623 
FDIndexReference(int newFDIndex, int oldFDIndex)624         public FDIndexReference(int newFDIndex, int oldFDIndex) {
625             this.newFDIndex = newFDIndex;
626             this.oldFDIndex = oldFDIndex;
627         }
628 
getNewFDIndex()629         public int getNewFDIndex() {
630             return newFDIndex;
631         }
632 
getOldFDIndex()633         public int getOldFDIndex() {
634             return oldFDIndex;
635         }
636     }
637 
createCharStringData()638     private void createCharStringData() throws IOException {
639         Map<String, DICTEntry> topDICT = cffReader.getTopDictEntries();
640 
641         CFFIndexData charStringsIndex = cffReader.getCharStringIndex();
642 
643         DICTEntry privateEntry = topDICT.get("Private");
644         if (privateEntry != null) {
645             int privateOffset = privateEntry.getOperands().get(1).intValue();
646             Map<String, DICTEntry> privateDICT = cffReader.getPrivateDict(privateEntry);
647 
648             if (privateDICT.get("Subrs") != null) {
649                 int localSubrOffset = privateOffset + privateDICT.get("Subrs").getOperands().get(0).intValue();
650                 localIndexSubr = cffReader.readIndex(localSubrOffset);
651             } else {
652                 localIndexSubr = cffReader.readIndex(null);
653             }
654         }
655 
656         globalIndexSubr = cffReader.getGlobalIndexSubr();
657 
658         //Create the two lists which are to store the local and global subroutines
659         subsetLocalIndexSubr = new ArrayList<byte[]>();
660         subsetGlobalIndexSubr = new ArrayList<byte[]>();
661 
662         //Create the new char string index
663         subsetCharStringsIndex = new ArrayList<byte[]>();
664 
665         localUniques = new ArrayList<Integer>();
666         globalUniques = new ArrayList<Integer>();
667         Map<Integer, Integer> gidHintMaskLengths = new HashMap<Integer, Integer>();
668         for (int gid : subsetGlyphs.keySet()) {
669             type2Parser = new Type2Parser();
670             byte[] data = charStringsIndex.getValue(gid);
671             preScanForSubsetIndexSize(data);
672             gidHintMaskLengths.put(gid, type2Parser.getMaskLength());
673         }
674 
675         //Store the size of each subset index and clear the unique arrays
676         subsetLocalSubrCount = localUniques.size();
677         subsetGlobalSubrCount = globalUniques.size();
678         localUniques.clear();
679         globalUniques.clear();
680 
681         for (int gid : subsetGlyphs.keySet()) {
682             byte[] data = charStringsIndex.getValue(gid);
683             type2Parser = new Type2Parser();
684             //Retrieve modified char string data and fill local / global subroutine arrays
685             type2Parser.setMaskLength(gidHintMaskLengths.get(gid));
686             data = readCharStringData(data, subsetLocalSubrCount);
687             subsetCharStringsIndex.add(data);
688         }
689     }
690 
691     static class Type2Parser {
692         /**
693          * logging instance
694          */
695         protected Log log = LogFactory.getLog(Type2Parser.class);
696 
697         private List<BytesNumber> stack = new ArrayList<BytesNumber>();
698         private int hstemCount;
699         private int vstemCount;
700         private int lastOp = -1;
701         private int maskLength = -1;
702 
pushOperand(BytesNumber v)703         public void pushOperand(BytesNumber v) {
704             stack.add(v);
705         }
706 
popOperand()707         public BytesNumber popOperand() {
708             return stack.remove(stack.size() - 1);
709         }
710 
clearStack()711         public void clearStack() {
712             stack.clear();
713         }
714 
getOperands(int numbers)715         public int[] getOperands(int numbers) {
716             int[] ret = new int[numbers];
717             while (numbers > 0) {
718                 numbers--;
719                 ret[numbers] = this.popOperand().getNumber();
720             }
721             return ret;
722         }
723 
setMaskLength(int maskLength)724         public void setMaskLength(int maskLength) {
725             this.maskLength = maskLength;
726         }
727 
getMaskLength()728         public int getMaskLength() {
729             // The number of data bytes for mask is exactly the number needed, one
730             // bit per hint, to reference the number of stem hints declared
731             // at the beginning of the charstring program.
732             if (maskLength > 0) {
733                 return maskLength;
734             }
735             return 1 + (hstemCount + vstemCount  - 1) / 8;
736         }
737 
exec(int b0, byte[] input, int curPos)738         private int exec(int b0, byte[] input, int curPos) throws IOException {
739             ByteArrayInputStream bis = new ByteArrayInputStream(input);
740             bis.skip(curPos + 1);
741             return exec(b0, bis);
742         }
743 
exec(int b0, InputStream data)744         public int exec(int b0, InputStream data) throws IOException {
745             int posDelta = 0;
746             if ((b0 >= 0 && b0 <= 27) || (b0 >= 29 && b0 <= 31)) {
747                 if (b0 == 12) {
748                     log.warn("May not guess the operand count correctly.");
749                     posDelta = 1;
750                 } else if (b0 == 1 || b0 == 18) {
751                     // hstem(hm) operator
752                     hstemCount += stack.size() / 2;
753                     clearStack();
754                 } else if (b0 == 19 || b0 == 20) {
755                     if (lastOp == 1 || lastOp == 18) {
756                         //If hstem and vstem hints are both declared at the beginning of
757                         //a charstring, and this sequence is followed directly by the
758                         //hintmask or cntrmask operators, the vstem hint operator need
759                         //not be included.
760                         vstemCount += stack.size() / 2;
761                     }
762                     clearStack();
763                     posDelta = getMaskLength();
764                 } else if (b0 == 3 || b0 == 23) {
765                     // vstem(hm) operator
766                     vstemCount += stack.size() / 2;
767                     clearStack();
768                 }
769                 if (b0 != 11 && b0 != 12) {
770                     lastOp = b0;
771                 }
772             } else if (b0 == 28 || (b0 >= 32 && b0 <= 255)) {
773                 BytesNumber operand = readNumber(b0, data);
774                 pushOperand(operand);
775                 posDelta = operand.getNumBytes() - 1;
776             } else {
777                 throw new UnsupportedOperationException("Operator:" + b0 + " is not supported");
778             }
779             return posDelta;
780         }
781 
readNumber(int b0, InputStream input)782         private BytesNumber readNumber(int b0, InputStream input) throws IOException {
783             if (b0 == 28) {
784                 int b1 = input.read();
785                 int b2 = input.read();
786                 return new BytesNumber((int) (short) (b1 << 8 | b2), 3);
787             } else if (b0 >= 32 && b0 <= 246) {
788                 return new BytesNumber(b0 - 139, 1);
789             } else if (b0 >= 247 && b0 <= 250) {
790                 int b1 = input.read();
791                 return new BytesNumber((b0 - 247) * 256 + b1 + 108, 2);
792             } else if (b0 >= 251 && b0 <= 254) {
793                 int b1 = input.read();
794                 return new BytesNumber(-(b0 - 251) * 256 - b1 - 108, 2);
795             } else if (b0 == 255) {
796                 int b1 = input.read();
797                 int b2 = input.read();
798                 int b3 = input.read();
799                 int b4 = input.read();
800                 return new BytesNumber((b1 << 24 | b2 << 16 | b3 << 8 | b4), 5);
801             } else {
802                 throw new IllegalArgumentException();
803             }
804         }
805     }
preScanForSubsetIndexSize(byte[] data)806     private void preScanForSubsetIndexSize(byte[] data) throws IOException {
807         boolean hasLocalSubroutines = localIndexSubr != null && localIndexSubr.getNumObjects() > 0;
808         boolean hasGlobalSubroutines = globalIndexSubr != null && globalIndexSubr.getNumObjects() > 0;
809         for (int dataPos = 0; dataPos < data.length; dataPos++) {
810             int b0 = data[dataPos] & 0xff;
811             if (b0 == LOCAL_SUBROUTINE && hasLocalSubroutines) {
812                 preScanForSubsetIndexSize(localIndexSubr, localUniques);
813             } else if (b0 == GLOBAL_SUBROUTINE && hasGlobalSubroutines) {
814                 preScanForSubsetIndexSize(globalIndexSubr, globalUniques);
815             } else  {
816                 dataPos += type2Parser.exec(b0, data, dataPos);
817             }
818         }
819     }
820 
preScanForSubsetIndexSize(CFFIndexData indexSubr, List<Integer> uniques)821     private void preScanForSubsetIndexSize(CFFIndexData indexSubr, List<Integer> uniques) throws IOException {
822         int subrNumber = getSubrNumber(indexSubr.getNumObjects(), type2Parser.popOperand().getNumber());
823         if (!uniques.contains(subrNumber) && subrNumber < indexSubr.getNumObjects()) {
824             uniques.add(subrNumber);
825         }
826         if (subrNumber < indexSubr.getNumObjects()) {
827             byte[] subr = indexSubr.getValue(subrNumber);
828             preScanForSubsetIndexSize(subr);
829         } else {
830             throw new IllegalArgumentException("callgsubr out of range");
831         }
832     }
833 
getSubrNumber(int numSubroutines, int operand)834     private int getSubrNumber(int numSubroutines, int operand) {
835         int bias = getBias(numSubroutines);
836         return bias + operand;
837     }
838 
readCharStringData(byte[] data, int subsetLocalSubrCount)839     private byte[] readCharStringData(byte[] data, int subsetLocalSubrCount) throws IOException {
840         boolean hasLocalSubroutines = localIndexSubr != null && localIndexSubr.getNumObjects() > 0;
841         boolean hasGlobalSubroutines = globalIndexSubr != null && globalIndexSubr.getNumObjects() > 0;
842         for (int dataPos = 0; dataPos < data.length; dataPos++) {
843             int b0 = data[dataPos] & 0xff;
844             if (b0 == 10 && hasLocalSubroutines) {
845                 BytesNumber operand = type2Parser.popOperand();
846                 int subrNumber = getSubrNumber(localIndexSubr.getNumObjects(), operand.getNumber());
847 
848                 int newRef = getNewRefForReference(subrNumber, localUniques, localIndexSubr, subsetLocalIndexSubr,
849                         subsetLocalSubrCount);
850 
851                 if (newRef != -1) {
852                     byte[] newData = constructNewRefData(dataPos, data, operand, subsetLocalSubrCount,
853                             newRef, new int[] {10});
854                     dataPos -= data.length - newData.length;
855                     data = newData;
856                 }
857             } else if (b0 == 29 && hasGlobalSubroutines) {
858                 BytesNumber operand = type2Parser.popOperand();
859                 int subrNumber = getSubrNumber(globalIndexSubr.getNumObjects(), operand.getNumber());
860 
861                 int newRef = getNewRefForReference(subrNumber, globalUniques, globalIndexSubr, subsetGlobalIndexSubr,
862                         subsetGlobalSubrCount);
863 
864                 if (newRef != -1) {
865                     byte[] newData = constructNewRefData(dataPos, data, operand, subsetGlobalSubrCount,
866                             newRef, new int[] {29});
867                     dataPos -= data.length - newData.length;
868                     data = newData;
869                 }
870             } else {
871                 dataPos += type2Parser.exec(b0, data, dataPos);
872             }
873         }
874 
875         //Return the data with the modified references to our arrays
876         return data;
877     }
878 
getNewRefForReference(int subrNumber, List<Integer> uniquesArray, CFFIndexData indexSubr, List<byte[]> subsetIndexSubr, int subrCount)879     private int getNewRefForReference(int subrNumber, List<Integer> uniquesArray,
880             CFFIndexData indexSubr, List<byte[]> subsetIndexSubr, int subrCount) throws IOException {
881         int newRef;
882         if (!uniquesArray.contains(subrNumber)) {
883             if (subrNumber < indexSubr.getNumObjects()) {
884                 byte[] subr = indexSubr.getValue(subrNumber);
885                 subr = readCharStringData(subr, subrCount);
886                 uniquesArray.add(subrNumber);
887                 subsetIndexSubr.add(subr);
888                 newRef = subsetIndexSubr.size() - 1;
889             } else {
890                 throw new IllegalArgumentException("subrNumber out of range");
891             }
892         } else {
893             newRef = uniquesArray.indexOf(subrNumber);
894         }
895         return newRef;
896     }
897 
getBias(int subrCount)898     private int getBias(int subrCount) {
899         if (subrCount < 1240) {
900             return 107;
901         } else if (subrCount < 33900) {
902             return 1131;
903         } else {
904             return 32768;
905         }
906     }
907 
constructNewRefData(int curDataPos, byte[] currentData, BytesNumber operand, int fullSubsetIndexSize, int curSubsetIndexSize, int[] operatorCode)908     private byte[] constructNewRefData(int curDataPos, byte[] currentData, BytesNumber operand,
909             int fullSubsetIndexSize, int curSubsetIndexSize, int[] operatorCode) throws IOException {
910         //Create the new array with the modified reference
911         ByteArrayOutputStream newData = new ByteArrayOutputStream();
912         int startRef = curDataPos - operand.getNumBytes();
913         int length = operand.getNumBytes() + 1;
914         int newBias = getBias(fullSubsetIndexSize);
915         int newRef = curSubsetIndexSize - newBias;
916         byte[] newRefBytes = createNewRef(newRef, operatorCode, -1, false);
917         newData.write(currentData, 0, startRef);
918         newData.write(newRefBytes);
919         newData.write(currentData, startRef + length, currentData.length - (startRef + length));
920         return newData.toByteArray();
921     }
922 
createNewRef(int newRef, int[] operatorCode, int forceLength, boolean isDict)923     public static byte[] createNewRef(int newRef, int[] operatorCode, int forceLength, boolean isDict) {
924         ByteArrayOutputStream newRefBytes = new ByteArrayOutputStream();
925         if ((forceLength == -1 && newRef >= -107 && newRef <= 107) || forceLength == 1) {
926             //The index values are 0 indexed
927             newRefBytes.write(newRef + 139);
928         } else if ((forceLength == -1 && newRef >= -1131 && newRef <= 1131) || forceLength == 2) {
929             if (newRef <= -876) {
930                 newRefBytes.write(254);
931             } else if (newRef <= -620) {
932                 newRefBytes.write(253);
933             } else if (newRef <= -364) {
934                 newRefBytes.write(252);
935             } else if (newRef <= -108) {
936                 newRefBytes.write(251);
937             } else if (newRef <= 363) {
938                 newRefBytes.write(247);
939             } else if (newRef <= 619) {
940                 newRefBytes.write(248);
941             } else if (newRef <= 875) {
942                 newRefBytes.write(249);
943             } else {
944                 newRefBytes.write(250);
945             }
946             if (newRef > 0) {
947                 newRefBytes.write(newRef - 108);
948             } else {
949                 newRefBytes.write(-newRef - 108);
950             }
951         } else if ((forceLength == -1 && newRef >= -32768 && newRef <= 32767) || forceLength == 3) {
952             newRefBytes.write(28);
953             newRefBytes.write(newRef >> 8);
954             newRefBytes.write(newRef);
955         } else {
956             if (isDict) {
957                 newRefBytes.write(29);
958             } else {
959                 newRefBytes.write(255);
960             }
961             newRefBytes.write(newRef >> 24);
962             newRefBytes.write(newRef >> 16);
963             newRefBytes.write(newRef >> 8);
964             newRefBytes.write(newRef);
965         }
966         for (int i : operatorCode) {
967             newRefBytes.write(i);
968         }
969         return newRefBytes.toByteArray();
970     }
971 
writeIndex(List<byte[]> dataArray)972     protected int writeIndex(List<byte[]> dataArray) {
973         int totLength = 1;
974         for (byte[] data : dataArray) {
975             totLength += data.length;
976         }
977         int offSize = getOffSize(totLength);
978         return writeIndex(dataArray, offSize);
979     }
980 
writeIndex(List<byte[]> dataArray, int offSize)981     protected int writeIndex(List<byte[]> dataArray, int offSize) {
982         int hdrTotal = 3;
983         //2 byte number of items
984         this.writeCard16(dataArray.size());
985         //Offset Size: 1 byte = 256, 2 bytes = 65536 etc.
986         //Offsets in the offset array are relative to the byte that precedes the object data.
987         //Therefore the first element of the offset array is always 1.
988         this.writeByte(offSize);
989         //Count the first offset 1
990         hdrTotal += offSize;
991         int total = 0;
992         int i = 0;
993         for (byte[] data : dataArray) {
994             hdrTotal += offSize;
995             int length = data.length;
996             switch (offSize) {
997             case 1:
998                 if (i == 0) {
999                     writeByte(1);
1000                 }
1001                 total += length;
1002                 writeByte(total + 1);
1003                 break;
1004             case 2:
1005                 if (i == 0) {
1006                     writeCard16(1);
1007                 }
1008                 total += length;
1009                 writeCard16(total + 1);
1010                 break;
1011             case 3:
1012                 if (i == 0) {
1013                     writeThreeByteNumber(1);
1014                 }
1015                 total += length;
1016                 writeThreeByteNumber(total + 1);
1017                 break;
1018             case 4:
1019                 if (i == 0) {
1020                     writeULong(1);
1021                 }
1022                 total += length;
1023                 writeULong(total + 1);
1024                 break;
1025             default:
1026                 throw new AssertionError("Offset Size was not an expected value.");
1027             }
1028             i++;
1029         }
1030         for (byte[] aDataArray : dataArray) {
1031             writeBytes(aDataArray);
1032         }
1033         return hdrTotal + total;
1034     }
1035 
getOffSize(int totLength)1036     private int getOffSize(int totLength) {
1037         int offSize = 1;
1038         if (totLength < (1 << 8)) {
1039             offSize = 1;
1040         } else if (totLength < (1 << 16)) {
1041             offSize = 2;
1042         } else if (totLength < (1 << 24)) {
1043             offSize = 3;
1044         } else {
1045             offSize = 4;
1046         }
1047         return offSize;
1048     }
1049     /**
1050      * A class used to store the last number operand and also it's size in bytes
1051      */
1052     static class BytesNumber {
1053         private int number;
1054         private int numBytes;
1055 
BytesNumber(int number, int numBytes)1056         public BytesNumber(int number, int numBytes) {
1057             this.number = number;
1058             this.numBytes = numBytes;
1059         }
1060 
getNumber()1061         public int getNumber() {
1062             return this.number;
1063         }
1064 
getNumBytes()1065         public int getNumBytes() {
1066             return this.numBytes;
1067         }
1068 
clearNumber()1069         public void clearNumber() {
1070             this.number = -1;
1071             this.numBytes = -1;
1072         }
1073 
toString()1074         public String toString() {
1075             return Integer.toString(number);
1076         }
1077 
1078         @Override
equals(Object entry)1079         public boolean equals(Object entry) {
1080             assert entry instanceof BytesNumber;
1081             BytesNumber bnEntry = (BytesNumber)entry;
1082             return this.number == bnEntry.getNumber()
1083                     && this.numBytes == bnEntry.getNumBytes();
1084         }
1085 
1086         @Override
hashCode()1087         public int hashCode() {
1088             int hash = 1;
1089             hash = hash * 17 + number;
1090             hash = hash * 31 + numBytes;
1091             return hash;
1092         }
1093     }
1094 
writeCharsetTable(boolean cidFont)1095     private void writeCharsetTable(boolean cidFont) throws IOException {
1096         if (cidFont) {
1097             writeByte(2);
1098             for (int entry : gidToSID.keySet()) {
1099                 if (entry == 0) {
1100                     continue;
1101                 }
1102                 writeCard16(entry);
1103                 writeCard16(gidToSID.size() - 1);
1104                 break;
1105             }
1106         } else {
1107             writeByte(0);
1108             for (int entry : gidToSID.values()) {
1109                 if (entry == 0) {
1110                     continue;
1111                 }
1112                 writeCard16(entry);
1113             }
1114         }
1115     }
1116 
writePrivateDict()1117     protected void writePrivateDict() throws IOException {
1118         Map<String, DICTEntry> topDICT = cffReader.getTopDictEntries();
1119 
1120         DICTEntry privateEntry = topDICT.get("Private");
1121         if (privateEntry != null) {
1122             writeBytes(cffReader.getPrivateDictBytes(privateEntry));
1123         }
1124     }
1125 
updateOffsets(Offsets offsets)1126     protected void updateOffsets(Offsets offsets) throws IOException {
1127         Map<String, DICTEntry> topDICT = cffReader.getTopDictEntries();
1128         Map<String, DICTEntry> privateDICT = null;
1129 
1130         DICTEntry privateEntry = topDICT.get("Private");
1131         if (privateEntry != null) {
1132             privateDICT = cffReader.getPrivateDict(privateEntry);
1133         }
1134 
1135         updateFixedOffsets(topDICT, offsets);
1136 
1137         if (privateDICT != null) {
1138             //Private index offset in the top dict
1139             int oldPrivateOffset = offsets.topDictData + privateEntry.getOffset();
1140             updateOffset(oldPrivateOffset + privateEntry.getOperandLengths().get(0),
1141                     privateEntry.getOperandLengths().get(1), offsets.privateDict);
1142 
1143             //Update the local subroutine index offset in the private dict
1144             DICTEntry subroutines = privateDICT.get("Subrs");
1145             if (subroutines != null) {
1146                 int oldLocalSubrOffset = offsets.privateDict + subroutines.getOffset();
1147                 updateOffset(oldLocalSubrOffset, subroutines.getOperandLength(),
1148                         (offsets.localIndex - offsets.privateDict));
1149             }
1150         }
1151     }
1152 
updateFixedOffsets(Map<String, DICTEntry> topDICT, Offsets offsets)1153     protected void updateFixedOffsets(Map<String, DICTEntry> topDICT, Offsets offsets) throws IOException {
1154         //Charset offset in the top dict
1155         DICTEntry charset = topDICT.get("charset");
1156         int oldCharsetOffset = offsets.topDictData + charset.getOffset();
1157         updateOffset(oldCharsetOffset, charset.getOperandLength(), offsets.charset);
1158 
1159         //Char string index offset in the private dict
1160         DICTEntry charString = topDICT.get("CharStrings");
1161         int oldCharStringOffset = offsets.topDictData + charString.getOffset();
1162         updateOffset(oldCharStringOffset, charString.getOperandLength(), offsets.charString);
1163 
1164         DICTEntry encodingEntry = topDICT.get("Encoding");
1165         if (encodingEntry != null && encodingEntry.getOperands().get(0).intValue() != 0
1166                 && encodingEntry.getOperands().get(0).intValue() != 1) {
1167             int oldEncodingOffset = offsets.topDictData + encodingEntry.getOffset();
1168             updateOffset(oldEncodingOffset, encodingEntry.getOperandLength(), offsets.encoding);
1169         }
1170     }
1171 
updateCIDOffsets(Offsets offsets)1172     protected void updateCIDOffsets(Offsets offsets) throws IOException {
1173         Map<String, DICTEntry> topDict = cffReader.getTopDictEntries();
1174 
1175         DICTEntry fdArrayEntry = topDict.get("FDArray");
1176         if (fdArrayEntry != null) {
1177             updateOffset(offsets.topDictData + fdArrayEntry.getOffset() - 1,
1178                     fdArrayEntry.getOperandLength(), offsets.fdArray);
1179         }
1180 
1181         DICTEntry fdSelect = topDict.get("FDSelect");
1182         if (fdSelect != null) {
1183             updateOffset(offsets.topDictData + fdSelect.getOffset() - 1,
1184                     fdSelect.getOperandLength(), offsets.fdSelect);
1185         }
1186 
1187         updateFixedOffsets(topDict, offsets);
1188     }
1189 
updateOffset(int position, int length, int replacement)1190     private void updateOffset(int position, int length, int replacement) throws IOException {
1191         byte[] outBytes = output.toByteArray();
1192         updateOffset(outBytes, position, length, replacement);
1193         output.reset();
1194         output.write(outBytes);
1195     }
1196 
updateOffset(byte[] out, int position, int length, int replacement)1197     private void updateOffset(byte[] out, int position, int length, int replacement) {
1198         switch (length) {
1199         case 1:
1200             out[position] = (byte)(replacement + 139);
1201             break;
1202         case 2:
1203             assert replacement <= 1131;
1204             if (replacement <= -876) {
1205                 out[position] = (byte)254;
1206             } else if (replacement <= -620) {
1207                 out[position] = (byte)253;
1208             } else if (replacement <= -364) {
1209                 out[position] = (byte)252;
1210             } else if (replacement <= -108) {
1211                 out[position] = (byte)251;
1212             } else if (replacement <= 363) {
1213                 out[position] = (byte)247;
1214             } else if (replacement <= 619) {
1215                 out[position] = (byte)248;
1216             } else if (replacement <= 875) {
1217                 out[position] = (byte)249;
1218             } else {
1219                 out[position] = (byte)250;
1220             }
1221             if (replacement > 0) {
1222                 out[position + 1] = (byte)(replacement - 108);
1223             } else {
1224                 out[position + 1] = (byte)(-replacement - 108);
1225             }
1226             break;
1227         case 3:
1228             assert replacement <= 32767;
1229             out[position] = (byte)28;
1230             out[position + 1] = (byte)((replacement >> 8) & 0xFF);
1231             out[position + 2] = (byte)(replacement & 0xFF);
1232             break;
1233         case 5:
1234             out[position] = (byte)29;
1235             out[position + 1] = (byte)((replacement >> 24) & 0xFF);
1236             out[position + 2] = (byte)((replacement >> 16) & 0xFF);
1237             out[position + 3] = (byte)((replacement >> 8) & 0xFF);
1238             out[position + 4] = (byte)(replacement & 0xFF);
1239             break;
1240         default:
1241         }
1242     }
1243 
1244     /**
1245      * Returns the parsed CFF data for the original font.
1246      * @return The CFFDataReader contaiing the parsed data
1247      */
getCFFReader()1248     public CFFDataReader getCFFReader() {
1249         return cffReader;
1250     }
1251 }
1252