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