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