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: PSFontUtils.java 1812122 2017-10-13 12:12:52Z ssteiner $ */ 19 20 package org.apache.fop.render.ps; 21 22 import java.io.ByteArrayInputStream; 23 import java.io.IOException; 24 import java.io.InputStream; 25 import java.util.ArrayList; 26 import java.util.HashMap; 27 import java.util.HashSet; 28 import java.util.List; 29 import java.util.Locale; 30 import java.util.Map; 31 import java.util.Set; 32 33 import org.apache.commons.io.IOUtils; 34 import org.apache.commons.logging.Log; 35 import org.apache.commons.logging.LogFactory; 36 import org.apache.fontbox.cff.CFFStandardString; 37 38 import org.apache.xmlgraphics.fonts.Glyphs; 39 import org.apache.xmlgraphics.java2d.GeneralGraphics2DImagePainter; 40 import org.apache.xmlgraphics.ps.DSCConstants; 41 import org.apache.xmlgraphics.ps.PSGenerator; 42 import org.apache.xmlgraphics.ps.PSResource; 43 import org.apache.xmlgraphics.ps.dsc.ResourceTracker; 44 45 import org.apache.fop.fonts.Base14Font; 46 import org.apache.fop.fonts.CFFToType1Font; 47 import org.apache.fop.fonts.CIDFontType; 48 import org.apache.fop.fonts.CIDSet; 49 import org.apache.fop.fonts.CMapSegment; 50 import org.apache.fop.fonts.CustomFont; 51 import org.apache.fop.fonts.EmbeddingMode; 52 import org.apache.fop.fonts.Font; 53 import org.apache.fop.fonts.FontInfo; 54 import org.apache.fop.fonts.FontTriplet; 55 import org.apache.fop.fonts.FontType; 56 import org.apache.fop.fonts.LazyFont; 57 import org.apache.fop.fonts.MultiByteFont; 58 import org.apache.fop.fonts.SingleByteEncoding; 59 import org.apache.fop.fonts.SingleByteFont; 60 import org.apache.fop.fonts.Typeface; 61 import org.apache.fop.fonts.cff.CFFDataReader; 62 import org.apache.fop.fonts.cff.CFFDataReader.DICTEntry; 63 import org.apache.fop.fonts.truetype.FontFileReader; 64 import org.apache.fop.fonts.truetype.OFFontLoader; 65 import org.apache.fop.fonts.truetype.OTFFile; 66 import org.apache.fop.fonts.truetype.OTFSubSetFile; 67 import org.apache.fop.fonts.truetype.OpenFont.PostScriptVersion; 68 import org.apache.fop.fonts.truetype.TTFFile; 69 import org.apache.fop.fonts.truetype.TTFOutputStream; 70 import org.apache.fop.fonts.truetype.TTFSubSetFile; 71 import org.apache.fop.fonts.type1.Type1SubsetFile; 72 import org.apache.fop.render.ps.fonts.PSTTFOutputStream; 73 import org.apache.fop.util.HexEncoder; 74 75 /** 76 * Utility code for font handling in PostScript. 77 */ 78 // @SuppressFBWarnings("NM_SAME_SIMPLE_NAME_AS_SUPERCLASS") 79 public class PSFontUtils extends org.apache.xmlgraphics.ps.PSFontUtils { 80 81 /** logging instance */ 82 protected static final Log log = LogFactory.getLog(PSFontUtils.class); 83 /** 84 * Generates the PostScript code for the font dictionary. This method should only be 85 * used if no "resource optimization" is performed, i.e. when the fonts are not embedded 86 * in a second pass. 87 * @param gen PostScript generator to use for output 88 * @param fontInfo available fonts 89 * @return a Map of PSResource instances representing all defined fonts (key: font key) 90 * @throws IOException in case of an I/O problem 91 */ writeFontDict(PSGenerator gen, FontInfo fontInfo)92 public static Map writeFontDict(PSGenerator gen, FontInfo fontInfo) 93 throws IOException { 94 return writeFontDict(gen, fontInfo, null); 95 } 96 97 /** 98 * Generates the PostScript code for the font dictionary. This method should only be 99 * used if no "resource optimization" is performed, i.e. when the fonts are not embedded 100 * in a second pass. 101 * @param gen PostScript generator to use for output 102 * @param fontInfo available fonts 103 * @param eventProducer to report events 104 * @return a Map of PSResource instances representing all defined fonts (key: font key) 105 * @throws IOException in case of an I/O problem 106 */ writeFontDict(PSGenerator gen, FontInfo fontInfo, PSEventProducer eventProducer)107 public static Map writeFontDict(PSGenerator gen, FontInfo fontInfo, 108 PSEventProducer eventProducer) throws IOException { 109 return writeFontDict(gen, fontInfo, fontInfo.getFonts(), true, eventProducer); 110 } 111 112 /** 113 * Generates the PostScript code for the font dictionary. This method assumes all used 114 * fonts and characters are known, i.e. when PostScript is generated with resource 115 * optimization turned on. 116 * @param gen PostScript generator to use for output 117 * @param fontInfo available fonts 118 * @param fonts the set of fonts to work with 119 * @param eventProducer the event producer 120 * @return a Map of PSResource instances representing all defined fonts (key: font key) 121 * @throws IOException in case of an I/O problem 122 */ writeFontDict(PSGenerator gen, FontInfo fontInfo, Map<String, Typeface> fonts, PSEventProducer eventProducer)123 public static Map writeFontDict(PSGenerator gen, FontInfo fontInfo, Map<String, Typeface> fonts, 124 PSEventProducer eventProducer) throws IOException { 125 return writeFontDict(gen, fontInfo, fonts, false, eventProducer); 126 } 127 128 /** 129 * Generates the PostScript code for the font dictionary. 130 * @param gen PostScript generator to use for output 131 * @param fontInfo available fonts 132 * @param fonts the set of fonts to work with 133 * @param encodeAllCharacters true if all characters shall be encoded using additional, 134 * generated encodings. 135 * @return a Map of PSResource instances representing all defined fonts (key: font key) 136 * @throws IOException in case of an I/O problem 137 */ writeFontDict(PSGenerator gen, FontInfo fontInfo, Map<String, Typeface> fonts, boolean encodeAllCharacters, PSEventProducer eventProducer)138 private static Map writeFontDict(PSGenerator gen, FontInfo fontInfo, 139 Map<String, Typeface> fonts, boolean encodeAllCharacters, PSEventProducer eventProducer) 140 throws IOException { 141 gen.commentln("%FOPBeginFontDict"); 142 143 Map fontResources = new HashMap(); 144 for (String key : fonts.keySet()) { 145 Typeface tf = getTypeFace(fontInfo, fonts, key); 146 PSFontResource fontResource = embedFont(gen, tf, eventProducer); 147 fontResources.put(key, fontResource); 148 149 if (tf instanceof SingleByteFont) { 150 SingleByteFont sbf = (SingleByteFont)tf; 151 152 if (encodeAllCharacters) { 153 sbf.encodeAllUnencodedCharacters(); 154 } 155 156 for (int i = 0, c = sbf.getAdditionalEncodingCount(); i < c; i++) { 157 SingleByteEncoding encoding = sbf.getAdditionalEncoding(i); 158 defineEncoding(gen, encoding); 159 String postFix = "_" + (i + 1); 160 PSResource derivedFontRes; 161 if (tf.getFontType() == FontType.TRUETYPE 162 && sbf.getTrueTypePostScriptVersion() != PostScriptVersion.V2) { 163 derivedFontRes = defineDerivedTrueTypeFont(gen, eventProducer, 164 tf.getEmbedFontName(), tf.getEmbedFontName() + postFix, encoding, 165 sbf.getCMap()); 166 } else { 167 derivedFontRes = defineDerivedFont(gen, tf.getEmbedFontName(), 168 tf.getEmbedFontName() + postFix, encoding.getName()); 169 } 170 fontResources.put(key + postFix, 171 PSFontResource.createFontResource(derivedFontRes)); 172 } 173 } 174 } 175 gen.commentln("%FOPEndFontDict"); 176 reencodeFonts(gen, fonts); 177 return fontResources; 178 } 179 reencodeFonts(PSGenerator gen, Map<String, Typeface> fonts)180 private static void reencodeFonts(PSGenerator gen, Map<String, Typeface> fonts) 181 throws IOException { 182 ResourceTracker tracker = gen.getResourceTracker(); 183 184 if (!tracker.isResourceSupplied(WINANSI_ENCODING_RESOURCE)) { 185 //Only out Base 14 fonts still use that 186 for (Typeface tf : fonts.values()) { 187 if (tf instanceof LazyFont) { 188 tf = ((LazyFont)tf).getRealFont(); 189 if (tf instanceof SingleByteFont 190 && ((SingleByteFont) tf).getEncoding().getName().equals("custom")) { 191 defineEncoding(gen, ((SingleByteFont) tf).getEncoding()); 192 } 193 } 194 } 195 defineWinAnsiEncoding(gen); 196 } 197 gen.commentln("%FOPBeginFontReencode"); 198 199 //Rewrite font encodings 200 for (Map.Entry<String, Typeface> e : fonts.entrySet()) { 201 String key = e.getKey(); 202 Typeface tf = e.getValue(); 203 if (tf instanceof LazyFont) { 204 tf = ((LazyFont)tf).getRealFont(); 205 if (tf == null) { 206 continue; 207 } 208 } 209 if (null == tf.getEncodingName()) { 210 //ignore (ZapfDingbats and Symbol used to run through here, kept for safety reasons) 211 } else if ("SymbolEncoding".equals(tf.getEncodingName())) { 212 //ignore (no encoding redefinition) 213 } else if ("ZapfDingbatsEncoding".equals(tf.getEncodingName())) { 214 //ignore (no encoding redefinition) 215 } else { 216 if (tf instanceof Base14Font) { 217 //Our Base 14 fonts don't use the default encoding 218 redefineFontEncoding(gen, tf.getEmbedFontName(), tf.getEncodingName()); 219 } else if (tf instanceof SingleByteFont) { 220 SingleByteFont sbf = (SingleByteFont)tf; 221 if (!sbf.isUsingNativeEncoding()) { 222 //Font has been configured to use an encoding other than the default one 223 redefineFontEncoding(gen, tf.getEmbedFontName(), tf.getEncodingName()); 224 } 225 } 226 } 227 } 228 gen.commentln("%FOPEndFontReencode"); 229 } 230 getTypeFace(FontInfo fontInfo, Map<String, Typeface> fonts, String key)231 private static Typeface getTypeFace(FontInfo fontInfo, Map<String, Typeface> fonts, 232 String key) { 233 Typeface tf = fonts.get(key); 234 if (tf instanceof LazyFont) { 235 tf = ((LazyFont)tf).getRealFont(); 236 } 237 if (tf == null) { 238 //This is to avoid an NPE if a malconfigured font is in the configuration but not 239 //used in the document. If it were used, we wouldn't get this far. 240 String fallbackKey = fontInfo.getInternalFontKey(Font.DEFAULT_FONT); 241 tf = fonts.get(fallbackKey); 242 } 243 return tf; 244 } 245 embedFont(PSGenerator gen, Typeface tf, PSEventProducer eventProducer)246 private static PSFontResource embedFont(PSGenerator gen, Typeface tf, PSEventProducer eventProducer) 247 throws IOException { 248 boolean embeddedFont = false; 249 FontType fontType = tf.getFontType(); 250 PSFontResource fontResource = null; 251 PSResource fontRes = new PSResource(PSResource.TYPE_FONT, tf.getEmbedFontName()); 252 if (!(fontType == FontType.TYPE1 || fontType == FontType.TRUETYPE 253 || fontType == FontType.TYPE0 || fontType == FontType.TYPE1C) || !(tf instanceof CustomFont)) { 254 gen.writeDSCComment(DSCConstants.INCLUDE_RESOURCE, fontRes); 255 fontResource = PSFontResource.createFontResource(fontRes); 256 return fontResource; 257 } 258 CustomFont cf = (CustomFont)tf; 259 if (isEmbeddable(cf)) { 260 List<InputStream> ins = getInputStreamOnFont(gen, cf); 261 if (ins != null) { 262 int i = 0; 263 for (InputStream in : ins) { 264 if (i > 0) { 265 fontRes = new PSResource(PSResource.TYPE_FONT, tf.getEmbedFontName() + "." + i); 266 } 267 if (fontType == FontType.TYPE0 || fontType == FontType.TYPE1C) { 268 if (((MultiByteFont) tf).isOTFFile()) { 269 checkPostScriptLevel3(gen, eventProducer, "OpenType CFF"); 270 embedType2CFF(gen, (MultiByteFont) tf, in); 271 } else { 272 if (gen.embedIdentityH()) { 273 checkPostScriptLevel3(gen, eventProducer, "TrueType"); 274 /* 275 * First CID-keyed font to be embedded; add 276 * %%IncludeResource: comment for ProcSet CIDInit. 277 */ 278 gen.includeProcsetCIDInitResource(); 279 } 280 PSResource cidFontResource; 281 cidFontResource = embedType2CIDFont(gen, 282 (MultiByteFont) tf, in); 283 fontResource = PSFontResource.createFontResource(fontRes, 284 gen.getProcsetCIDInitResource(), gen.getIdentityHCMapResource(), 285 cidFontResource); 286 } 287 } 288 gen.writeDSCComment(DSCConstants.BEGIN_RESOURCE, fontRes); 289 if (fontType == FontType.TYPE1) { 290 embedType1Font(gen, (CustomFont) tf, in); 291 if (fontResource == null) { 292 fontResource = PSFontResource.createFontResource(fontRes); 293 } 294 } else if (fontType == FontType.TRUETYPE) { 295 embedTrueTypeFont(gen, (SingleByteFont) tf, in); 296 fontResource = PSFontResource.createFontResource(fontRes); 297 } else if (!((MultiByteFont) tf).isOTFFile()) { 298 composeType0Font(gen, (MultiByteFont) tf); 299 } 300 gen.writeDSCComment(DSCConstants.END_RESOURCE); 301 gen.getResourceTracker().registerSuppliedResource(fontRes); 302 embeddedFont = true; 303 i++; 304 } 305 } else { 306 gen.commentln("%WARNING: Could not embed font: " + cf.getEmbedFontName()); 307 log.warn("Font " + cf.getEmbedFontName() + " is marked as supplied in the" 308 + " PostScript file but could not be embedded!"); 309 } 310 } 311 if (!embeddedFont) { 312 gen.writeDSCComment(DSCConstants.INCLUDE_RESOURCE, fontRes); 313 fontResource = PSFontResource.createFontResource(fontRes); 314 } 315 return fontResource; 316 } 317 checkPostScriptLevel3(PSGenerator gen, PSEventProducer eventProducer, String fontType)318 private static void checkPostScriptLevel3(PSGenerator gen, PSEventProducer eventProducer, 319 String fontType) { 320 if (gen.getPSLevel() < 3) { 321 if (eventProducer != null) { 322 eventProducer.postscriptLevel3Needed(gen); 323 } else { 324 throw new IllegalStateException("PostScript Level 3 is" 325 + " required to use " + fontType + " fonts," 326 + " configured level is " 327 + gen.getPSLevel()); 328 } 329 } 330 } 331 embedType1Font(PSGenerator gen, CustomFont font, InputStream fontStream)332 private static void embedType1Font(PSGenerator gen, CustomFont font, 333 InputStream fontStream) throws IOException { 334 if (font.getEmbeddingMode() == EmbeddingMode.AUTO) { 335 font.setEmbeddingMode(EmbeddingMode.FULL); 336 } 337 byte[] fullFont = IOUtils.toByteArray(fontStream); 338 fontStream = new ByteArrayInputStream(fullFont); 339 boolean embed = true; 340 if (font.getEmbeddingMode() == EmbeddingMode.SUBSET) { 341 Type1SubsetFile subset = new Type1SubsetFile(); 342 byte[] byteSubset = subset.createSubset(fontStream, (SingleByteFont) font); 343 fontStream = new ByteArrayInputStream(byteSubset); 344 } 345 embedType1Font(gen, fontStream); 346 if (font.getEmbeddingMode() == EmbeddingMode.SUBSET) { 347 writeEncoding(gen, (SingleByteFont) font); 348 } 349 } 350 writeEncoding(PSGenerator gen, SingleByteFont font)351 private static void writeEncoding(PSGenerator gen, SingleByteFont font) throws IOException { 352 String psName = font.getEmbedFontName(); 353 gen.writeln("/" + psName + ".0.enc [ "); 354 int lengthCount = 0; 355 int charCount = 1; 356 int encodingCount = 0; 357 StringBuilder line = new StringBuilder(); 358 int lastGid = 0; 359 Set<Integer> keySet = font.getUsedGlyphNames().keySet(); 360 for (int gid : keySet) { 361 for (int i = lastGid; i < gid - 1; i++) { 362 line.append("/.notdef "); 363 lengthCount++; 364 if (lengthCount == 8) { 365 gen.writeln(line.toString()); 366 line = new StringBuilder(); 367 lengthCount = 0; 368 } 369 } 370 lastGid = gid; 371 line.append(font.getUsedGlyphNames().get(gid) + " "); 372 lengthCount++; 373 charCount++; 374 if (lengthCount == 8) { 375 gen.writeln(line.toString()); 376 line = new StringBuilder(); 377 lengthCount = 0; 378 } 379 if (charCount > 256) { 380 encodingCount++; 381 charCount = 1; 382 gen.writeln(line.toString()); 383 line = new StringBuilder(); 384 lengthCount = 0; 385 gen.writeln("] def"); 386 gen.writeln(String.format("/%s.%d %s.%d.enc /%s RE", psName, 387 encodingCount - 1, psName, encodingCount - 1, psName)); 388 gen.writeln("/" + psName + "." + encodingCount + ".enc [ "); 389 } 390 } 391 gen.writeln(line.toString()); 392 gen.writeln("] def"); 393 gen.writeln(String.format("/%s.%d %s.%d.enc /%s RE", psName, encodingCount, 394 psName, encodingCount, psName)); 395 } 396 embedTrueTypeFont(PSGenerator gen, SingleByteFont font, InputStream fontStream)397 private static void embedTrueTypeFont(PSGenerator gen, 398 SingleByteFont font, InputStream fontStream) throws IOException { 399 /* See Adobe Technical Note #5012, "The Type 42 Font Format Specification" */ 400 gen.commentln("%!PS-TrueTypeFont-65536-65536-1"); // TODO TrueType & font versions 401 gen.writeln("11 dict begin"); 402 if (font.getEmbeddingMode() == EmbeddingMode.AUTO) { 403 font.setEmbeddingMode(EmbeddingMode.SUBSET); 404 } 405 FontFileReader reader = new FontFileReader(fontStream); 406 TTFFile ttfFile = new TTFFile(); 407 ttfFile.readFont(reader, font.getFullName()); 408 createType42DictionaryEntries(gen, font, font.getCMap(), ttfFile); 409 gen.writeln("FontName currentdict end definefont pop"); 410 } 411 createType42DictionaryEntries(PSGenerator gen, CustomFont font, CMapSegment[] cmap, TTFFile ttfFile)412 private static void createType42DictionaryEntries(PSGenerator gen, CustomFont font, 413 CMapSegment[] cmap, TTFFile ttfFile) throws IOException { 414 gen.write("/FontName /"); 415 gen.write(font.getEmbedFontName()); 416 gen.writeln(" def"); 417 gen.writeln("/PaintType 0 def"); 418 gen.writeln("/FontMatrix [1 0 0 1 0 0] def"); 419 writeFontBBox(gen, font); 420 gen.writeln("/FontType 42 def"); 421 gen.writeln("/Encoding 256 array"); 422 gen.writeln("0 1 255{1 index exch/.notdef put}for"); 423 boolean buildCharStrings; 424 Set<String> glyphNames = new HashSet<String>(); 425 if (font.getFontType() == FontType.TYPE0 && font.getEmbeddingMode() != EmbeddingMode.FULL) { 426 //"/Encoding" is required but ignored for CID fonts 427 //so we keep it minimal to save space 428 buildCharStrings = false; 429 } else { 430 buildCharStrings = true; 431 for (int i = 0; i < Glyphs.WINANSI_ENCODING.length; i++) { 432 gen.write("dup "); 433 gen.write(i); 434 gen.write(" /"); 435 String glyphName = Glyphs.charToGlyphName(Glyphs.WINANSI_ENCODING[i]); 436 if (glyphName.equals("")) { 437 gen.write(Glyphs.NOTDEF); 438 } else { 439 gen.write(glyphName); 440 glyphNames.add(glyphName); 441 } 442 gen.writeln(" put"); 443 } 444 } 445 gen.writeln("readonly def"); 446 TTFOutputStream ttfOut = new PSTTFOutputStream(gen); 447 ttfFile.stream(ttfOut); 448 449 buildCharStrings(gen, buildCharStrings, cmap, glyphNames, font); 450 } 451 buildCharStrings(PSGenerator gen, boolean buildCharStrings, CMapSegment[] cmap, Set<String> glyphNames, CustomFont font)452 private static void buildCharStrings(PSGenerator gen, boolean buildCharStrings, 453 CMapSegment[] cmap, Set<String> glyphNames, CustomFont font) throws IOException { 454 gen.write("/CharStrings "); 455 if (!buildCharStrings) { 456 gen.write(1); 457 } else if (font.getEmbeddingMode() != EmbeddingMode.FULL) { 458 int charCount = 1; //1 for .notdef 459 for (CMapSegment segment : cmap) { 460 charCount += segment.getUnicodeEnd() - segment.getUnicodeStart() + 1; 461 } 462 gen.write(charCount); 463 } else { 464 gen.write(font.getCMap().length); 465 } 466 gen.writeln(" dict dup begin"); 467 gen.write("/"); 468 gen.write(Glyphs.NOTDEF); 469 gen.writeln(" 0 def"); // .notdef always has to be at index 0 470 if (!buildCharStrings) { 471 // If we're not building the full CharStrings we can end here 472 gen.writeln("end readonly def"); 473 return; 474 } 475 if (font.getEmbeddingMode() != EmbeddingMode.FULL) { 476 //Only performed in singly-byte mode, ignored for CID fonts 477 for (CMapSegment segment : cmap) { 478 int glyphIndex = segment.getGlyphStartIndex(); 479 for (int ch = segment.getUnicodeStart(); ch <= segment.getUnicodeEnd(); ch++) { 480 char ch16 = (char)ch; //TODO Handle Unicode characters beyond 16bit 481 String glyphName = Glyphs.charToGlyphName(ch16); 482 if ("".equals(glyphName)) { 483 glyphName = "u" + Integer.toHexString(ch).toUpperCase(Locale.ENGLISH); 484 } 485 writeGlyphDefs(gen, glyphName, glyphIndex); 486 487 glyphIndex++; 488 } 489 } 490 } else { 491 for (String name : glyphNames) { 492 writeGlyphDefs(gen, name, 493 getGlyphIndex(Glyphs.getUnicodeSequenceForGlyphName(name).charAt(0), 494 font.getCMap())); 495 } 496 } 497 gen.writeln("end readonly def"); 498 } 499 writeGlyphDefs(PSGenerator gen, String glyphName, int glyphIndex)500 private static void writeGlyphDefs(PSGenerator gen, String glyphName, int glyphIndex) 501 throws IOException { 502 gen.write("/"); 503 gen.write(glyphName); 504 gen.write(" "); 505 gen.write(glyphIndex); 506 gen.writeln(" def"); 507 } 508 getGlyphIndex(char c, CMapSegment[] cmap)509 private static int getGlyphIndex(char c, CMapSegment[] cmap) { 510 for (CMapSegment segment : cmap) { 511 if (segment.getUnicodeStart() <= c && c <= segment.getUnicodeEnd()) { 512 return segment.getGlyphStartIndex() + c - segment.getUnicodeStart(); 513 } 514 } 515 return 0; 516 } 517 composeType0Font(PSGenerator gen, MultiByteFont font)518 private static void composeType0Font(PSGenerator gen, MultiByteFont font) throws IOException { 519 String psName = font.getEmbedFontName(); 520 gen.write("/"); 521 gen.write(psName); 522 gen.write(" /Identity-H [/"); 523 gen.write(psName); 524 gen.writeln("] composefont pop"); 525 } 526 embedType2CFF(PSGenerator gen, MultiByteFont font, InputStream fontStream)527 private static void embedType2CFF(PSGenerator gen, 528 MultiByteFont font, InputStream fontStream) throws IOException { 529 FontFileReader reader = new FontFileReader(fontStream); 530 String psName; 531 CFFDataReader cffReader = new CFFDataReader(reader); 532 if (cffReader.getFDSelect() != null) { 533 throw new UnsupportedOperationException("CID-Keyed OTF CFF fonts are not supported" 534 + " for PostScript output."); 535 } 536 537 byte[] bytes; 538 if (font.getEmbeddingMode() == EmbeddingMode.FULL) { 539 font.setFontName(new String(cffReader.getNameIndex().getValue(0))); 540 psName = font.getEmbedFontName(); 541 Map<String, DICTEntry> topDICT = cffReader.getTopDictEntries(); 542 int charsetOffset = topDICT.get("charset").getOperands().get(0).intValue(); 543 for (int gid = 0; gid < cffReader.getCharStringIndex().getNumObjects(); gid++) { 544 int sid = cffReader.getSIDFromGID(charsetOffset, gid); 545 546 //Check whether the SID falls into the standard string set 547 if (sid < 391) { 548 font.mapUsedGlyphName(gid, 549 CFFStandardString.getName(sid)); 550 } else { 551 int index = sid - 391; 552 if (index < cffReader.getStringIndex().getNumObjects()) { 553 font.mapUsedGlyphName(gid, 554 new String(cffReader.getStringIndex().getValue(index))); 555 } else { 556 font.mapUsedGlyphName(gid, ".notdef"); 557 } 558 } 559 } 560 bytes = OTFFile.getCFFData(reader); 561 } else { 562 psName = font.getEmbedFontName(); 563 OTFSubSetFile otfFile = new OTFSubSetFile(); 564 otfFile.readFont(reader, psName, font); 565 bytes = otfFile.getFontSubset(); 566 } 567 568 gen.writeln("%!PS-Adobe-3.0 Resource-FontSet"); 569 gen.writeln("%%DocumentNeedResources:ProcSet(FontSetInit)"); 570 gen.writeln("%%Title:(FontSet/" + psName + ")"); 571 gen.writeln("%%Version: 1.000"); 572 gen.writeln("%%EndComments"); 573 gen.writeln("%%IncludeResource:ProcSet(FontSetInit)"); 574 gen.writeln("%%BeginResource: FontSet (" + psName + ")"); 575 gen.writeln("/FontSetInit /ProcSet findresource begin"); 576 //Next line + 1 577 String fontDeclaration = "/" + psName + " " + bytes.length + " StartData"; 578 gen.writeln("%%BeginData: " + (fontDeclaration.length() + 1 + bytes.length) + " Binary Bytes"); 579 gen.writeln(fontDeclaration); 580 gen.writeByteArr(bytes); 581 gen.writeln("%%EndData"); 582 gen.writeln("%%EndResource"); 583 584 gen.writeln("/" + psName + ".0.enc [ "); 585 int lengthCount = 0; 586 int charCount = 1; 587 int encodingCount = 0; 588 String line = ""; 589 for (int gid : font.getUsedGlyphNames().keySet()) { 590 line += "/" + font.getUsedGlyphNames().get(gid) + " "; 591 lengthCount++; 592 charCount++; 593 if (lengthCount == 8) { 594 gen.writeln(line); 595 line = ""; 596 lengthCount = 0; 597 } 598 if (charCount > 256) { 599 encodingCount++; 600 charCount = 1; 601 gen.writeln(line); 602 line = ""; 603 lengthCount = 0; 604 gen.writeln("] def"); 605 gen.writeln(String.format("/%s.%d %s.%d.enc /%s RE", psName, 606 encodingCount - 1, psName, encodingCount - 1, psName)); 607 gen.writeln("/" + psName + "." + encodingCount + ".enc [ "); 608 } 609 } 610 gen.writeln(line); 611 gen.writeln("] def"); 612 gen.writeln(String.format("/%s.%d %s.%d.enc /%s RE", psName, encodingCount, 613 psName, encodingCount, psName)); 614 } 615 embedType2CIDFont(PSGenerator gen, MultiByteFont font, InputStream fontStream)616 private static PSResource embedType2CIDFont(PSGenerator gen, 617 MultiByteFont font, InputStream fontStream) throws IOException { 618 assert font.getCIDType() == CIDFontType.CIDTYPE2; 619 620 String psName = font.getEmbedFontName(); 621 gen.write("%%BeginResource: CIDFont "); 622 gen.writeln(psName); 623 624 gen.write("%%Title: ("); 625 gen.write(psName); 626 gen.writeln(" Adobe Identity 0)"); 627 628 gen.writeln("%%Version: 1"); // TODO use font revision? 629 gen.writeln("/CIDInit /ProcSet findresource begin"); 630 gen.writeln("20 dict begin"); 631 632 gen.write("/CIDFontName /"); 633 gen.write(psName); 634 gen.writeln(" def"); 635 636 gen.writeln("/CIDFontVersion 1 def"); // TODO same as %%Version above 637 638 gen.write("/CIDFontType "); 639 gen.write(font.getCIDType().getValue()); 640 gen.writeln(" def"); 641 642 gen.writeln("/CIDSystemInfo 3 dict dup begin"); 643 gen.writeln(" /Registry (Adobe) def"); 644 gen.writeln(" /Ordering (Identity) def"); 645 gen.writeln(" /Supplement 0 def"); 646 gen.writeln("end def"); 647 648 // TODO UIDBase (and UIDOffset in CMap) necessary if PostScript Level 1 & 2 649 // interpreters are to be supported 650 // (Level 1: with composite font extensions; Level 2: those that do not offer 651 // native mode support for CID-keyed fonts) 652 653 // TODO XUID (optional but strongly recommended) 654 655 // TODO /FontInfo 656 657 gen.write("/CIDCount "); 658 CIDSet cidSet = font.getCIDSet(); 659 int numberOfGlyphs = cidSet.getNumberOfGlyphs(); 660 gen.write(numberOfGlyphs); 661 gen.writeln(" def"); 662 gen.writeln("/GDBytes 2 def"); // TODO always 2? 663 gen.writeln("/CIDMap [<"); 664 int colCount = 0; 665 int lineCount = 1; 666 int nextBitSet = 0; 667 int previousBitSet = 0; 668 for (int cid = 0; cid < numberOfGlyphs; cid++) { 669 if (colCount++ == 20) { 670 gen.newLine(); 671 colCount = 1; 672 if (lineCount++ == 800) { 673 gen.writeln("> <"); 674 lineCount = 1; 675 } 676 } 677 String gid; 678 if (font.getEmbeddingMode() != EmbeddingMode.FULL) { 679 gid = HexEncoder.encode(cid, 4); 680 } else { 681 previousBitSet = nextBitSet; 682 nextBitSet = cidSet.getGlyphIndices().nextSetBit(nextBitSet); 683 while (previousBitSet++ < nextBitSet) { 684 // if there are gaps in the indices we pad them with zeros 685 gen.write("0000"); 686 cid++; 687 if (colCount++ == 20) { 688 gen.newLine(); 689 colCount = 1; 690 if (lineCount++ == 800) { 691 gen.writeln("> <"); 692 lineCount = 1; 693 } 694 } 695 } 696 gid = HexEncoder.encode(nextBitSet, 4); 697 nextBitSet++; 698 } 699 gen.write(gid); 700 } 701 gen.writeln(">] def"); 702 FontFileReader reader = new FontFileReader(fontStream); 703 String header = OFFontLoader.readHeader(reader); 704 705 TTFFile ttfFile; 706 if (font.getEmbeddingMode() != EmbeddingMode.FULL) { 707 ttfFile = new TTFSubSetFile(); 708 //Change the TTFFile to have the abstract method for TTFSubSetFile 709 ((TTFSubSetFile)ttfFile).readFont(reader, font.getTTCName(), header, font.getUsedGlyphs()); 710 } else { 711 ttfFile = new TTFFile(); 712 ttfFile.readFont(reader, font.getTTCName()); 713 } 714 715 createType42DictionaryEntries(gen, font, new CMapSegment[0], ttfFile); 716 gen.writeln("CIDFontName currentdict end /CIDFont defineresource pop"); 717 gen.writeln("end"); 718 gen.writeln("%%EndResource"); 719 PSResource cidFontResource = new PSResource(PSResource.TYPE_CIDFONT, psName); 720 gen.getResourceTracker().registerSuppliedResource(cidFontResource); 721 return cidFontResource; 722 } 723 writeFontBBox(PSGenerator gen, CustomFont font)724 private static void writeFontBBox(PSGenerator gen, CustomFont font) throws IOException { 725 int[] bbox = font.getFontBBox(); 726 gen.write("/FontBBox["); 727 for (int i = 0; i < 4; i++) { 728 gen.write(" "); 729 gen.write(bbox[i]); 730 } 731 gen.writeln(" ] def"); 732 } 733 isEmbeddable(CustomFont font)734 private static boolean isEmbeddable(CustomFont font) { 735 return font.isEmbeddable(); 736 } 737 getInputStreamOnFont(PSGenerator gen, CustomFont font)738 private static List<InputStream> getInputStreamOnFont(PSGenerator gen, CustomFont font) 739 throws IOException { 740 if (isEmbeddable(font)) { 741 List<InputStream> fonts = new ArrayList<InputStream>(); 742 InputStream in = font.getInputStream(); 743 if (in == null) { 744 if (font instanceof CFFToType1Font) { 745 return ((CFFToType1Font) font).getInputStreams(); 746 } 747 return null; 748 } 749 //Make sure the InputStream is decorated with a BufferedInputStream 750 if (!(in instanceof java.io.BufferedInputStream)) { 751 in = new java.io.BufferedInputStream(in); 752 } 753 fonts.add(in); 754 return fonts; 755 } else { 756 return null; 757 } 758 } 759 760 /** 761 * Determines the set of fonts that will be supplied with the PS file and registers them 762 * with the resource tracker. All the fonts that are being processed are returned as a Map. 763 * @param resTracker the resource tracker 764 * @param fontInfo available fonts 765 * @param fonts the set of fonts to work with 766 * @return a Map of PSResource instances representing all defined fonts (key: font key) 767 */ determineSuppliedFonts(ResourceTracker resTracker, FontInfo fontInfo, Map<String, Typeface> fonts)768 public static Map determineSuppliedFonts(ResourceTracker resTracker, 769 FontInfo fontInfo, Map<String, Typeface> fonts) { 770 Map fontResources = new java.util.HashMap(); 771 for (String key : fonts.keySet()) { 772 Typeface tf = getTypeFace(fontInfo, fonts, key); 773 PSResource fontRes = new PSResource("font", tf.getEmbedFontName()); 774 fontResources.put(key, fontRes); 775 FontType fontType = tf.getFontType(); 776 if (fontType == FontType.TYPE1 || fontType == FontType.TRUETYPE 777 || fontType == FontType.TYPE0) { 778 if (tf instanceof CustomFont) { 779 CustomFont cf = (CustomFont)tf; 780 if (isEmbeddable(cf)) { 781 if (fontType == FontType.TYPE0) { 782 resTracker.registerSuppliedResource( 783 new PSResource(PSResource.TYPE_CIDFONT, tf.getEmbedFontName())); 784 resTracker.registerSuppliedResource( 785 new PSResource(PSResource.TYPE_CMAP, "Identity-H")); 786 } 787 resTracker.registerSuppliedResource(fontRes); 788 } 789 if (tf instanceof SingleByteFont) { 790 SingleByteFont sbf = (SingleByteFont)tf; 791 for (int i = 0, c = sbf.getAdditionalEncodingCount(); i < c; i++) { 792 SingleByteEncoding encoding = sbf.getAdditionalEncoding(i); 793 PSResource encodingRes = new PSResource( 794 PSResource.TYPE_ENCODING, encoding.getName()); 795 resTracker.registerSuppliedResource(encodingRes); 796 PSResource derivedFontRes = new PSResource( 797 PSResource.TYPE_FONT, tf.getEmbedFontName() + "_" + (i + 1)); 798 resTracker.registerSuppliedResource(derivedFontRes); 799 } 800 } 801 } 802 } 803 } 804 return fontResources; 805 } 806 807 /** 808 * Defines the single-byte encoding for use in PostScript files. 809 * @param gen the PostScript generator 810 * @param encoding the single-byte encoding 811 * @return the PSResource instance that represents the encoding 812 * @throws IOException In case of an I/O problem 813 */ defineEncoding(PSGenerator gen, SingleByteEncoding encoding)814 public static PSResource defineEncoding(PSGenerator gen, SingleByteEncoding encoding) 815 throws IOException { 816 PSResource res = new PSResource(PSResource.TYPE_ENCODING, encoding.getName()); 817 gen.writeDSCComment(DSCConstants.BEGIN_RESOURCE, res); 818 gen.writeln("/" + encoding.getName() + " ["); 819 String[] charNames = encoding.getCharNameMap(); 820 for (int i = 0; i < 256; i++) { 821 if (i > 0) { 822 if ((i % 5) == 0) { 823 gen.newLine(); 824 } else { 825 gen.write(" "); 826 } 827 } 828 String glyphname = null; 829 if (i < charNames.length) { 830 glyphname = charNames[i]; 831 } 832 if (glyphname == null || "".equals(glyphname)) { 833 glyphname = Glyphs.NOTDEF; 834 } 835 gen.write("/"); 836 gen.write(glyphname); 837 } 838 gen.newLine(); 839 gen.writeln("] def"); 840 gen.writeDSCComment(DSCConstants.END_RESOURCE); 841 gen.getResourceTracker().registerSuppliedResource(res); 842 return res; 843 } 844 845 /** 846 * Derives a new font based on an existing font with a given encoding. The encoding must 847 * have been registered before. 848 * @param gen the PostScript generator 849 * @param baseFontName the font name of the font to derive from 850 * @param fontName the font name of the new font to be define 851 * @param encoding the new encoding (must be predefined in the PS file) 852 * @return the PSResource representing the derived font 853 * @throws IOException In case of an I/O problem 854 */ defineDerivedFont( PSGenerator gen, String baseFontName, String fontName, String encoding)855 public static PSResource defineDerivedFont( 856 PSGenerator gen, String baseFontName, String fontName, String encoding) 857 throws IOException { 858 PSResource res = new PSResource(PSResource.TYPE_FONT, fontName); 859 gen.writeDSCComment(DSCConstants.BEGIN_RESOURCE, res); 860 gen.commentln("%XGCDependencies: font " + baseFontName); 861 gen.commentln("%XGC+ encoding " + encoding); 862 gen.writeln("/" + baseFontName + " findfont"); 863 gen.writeln("dup length dict begin"); 864 gen.writeln(" {1 index /FID ne {def} {pop pop} ifelse} forall"); 865 gen.writeln(" /Encoding " + encoding + " def"); 866 gen.writeln(" currentdict"); 867 gen.writeln("end"); 868 gen.writeln("/" + fontName + " exch definefont pop"); 869 gen.writeDSCComment(DSCConstants.END_RESOURCE); 870 gen.getResourceTracker().registerSuppliedResource(res); 871 return res; 872 } 873 defineDerivedTrueTypeFont(PSGenerator gen, PSEventProducer eventProducer, String baseFontName, String fontName, SingleByteEncoding encoding, CMapSegment[] cmap)874 private static PSResource defineDerivedTrueTypeFont(PSGenerator gen, 875 PSEventProducer eventProducer, String baseFontName, String fontName, 876 SingleByteEncoding encoding, CMapSegment[] cmap) throws IOException { 877 checkPostScriptLevel3(gen, eventProducer, "TrueType"); 878 PSResource res = new PSResource(PSResource.TYPE_FONT, fontName); 879 gen.writeDSCComment(DSCConstants.BEGIN_RESOURCE, res); 880 gen.commentln("%XGCDependencies: font " + baseFontName); 881 gen.commentln("%XGC+ encoding " + encoding.getName()); 882 gen.writeln("/" + baseFontName + " findfont"); 883 gen.writeln("dup length dict begin"); 884 gen.writeln(" {1 index /FID ne {def} {pop pop} ifelse} forall"); 885 gen.writeln(" /Encoding " + encoding.getName() + " def"); 886 887 gen.writeln(" /CharStrings 256 dict dup begin"); 888 String[] charNameMap = encoding.getCharNameMap(); 889 char[] unicodeCharMap = encoding.getUnicodeCharMap(); 890 assert charNameMap.length == unicodeCharMap.length; 891 for (int i = 0; i < charNameMap.length; i++) { 892 String glyphName = charNameMap[i]; 893 gen.write(" /"); 894 gen.write(glyphName); 895 gen.write(" "); 896 if (glyphName.equals(".notdef")) { 897 gen.write(0); 898 } else { 899 gen.write(getGlyphIndex(unicodeCharMap[i], cmap)); 900 } 901 gen.writeln(" def"); 902 } 903 gen.writeln(" end readonly def"); 904 905 gen.writeln(" currentdict"); 906 gen.writeln("end"); 907 gen.writeln("/" + fontName + " exch definefont pop"); 908 gen.writeDSCComment(DSCConstants.END_RESOURCE); 909 gen.getResourceTracker().registerSuppliedResource(res); 910 return res; 911 } 912 addFallbackFonts(FontInfo fontInfo, GeneralGraphics2DImagePainter painter)913 public static void addFallbackFonts(FontInfo fontInfo, GeneralGraphics2DImagePainter painter) throws IOException { 914 for (Map.Entry<FontTriplet, String> x : fontInfo.getFontTriplets().entrySet()) { 915 String name = x.getKey().getName(); 916 Typeface typeface = fontInfo.getFonts().get(x.getValue()); 917 painter.addFallbackFont(name, typeface); 918 } 919 } 920 } 921