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 package org.apache.fop.render.ps;
18 
19 import java.io.IOException;
20 import java.text.DecimalFormat;
21 import java.text.DecimalFormatSymbols;
22 import java.text.NumberFormat;
23 import java.util.Collection;
24 import java.util.Locale;
25 import java.util.Map;
26 
27 import org.apache.fontbox.cff.CFFCIDFont;
28 import org.apache.fontbox.cff.CFFFont;
29 import org.apache.fontbox.cff.CFFType1Font;
30 import org.apache.fontbox.cff.DataOutput;
31 import org.apache.fontbox.cff.Type1FontUtil;
32 
33 /**
34  * This class represents a formatter for a given Type1 font.
35  * author Villu Ruusmann
36  * @version $Revision: 1.0 $
37  */
38 public final class Type1FontFormatter {
39     private Map<Integer, Integer> gids;
40 
Type1FontFormatter(Map<Integer, Integer> gids)41     public Type1FontFormatter(Map<Integer, Integer> gids) {
42         this.gids = gids;
43     }
44 
45     /**
46      * Read and convert a given CFFFont.
47      * @param font the given CFFFont
48      * @param i
49      * @return the Type1 font
50      * @throws IOException if an error occurs during reading the given font
51      */
format(CFFFont font, String i)52     public byte[] format(CFFFont font, String i) throws IOException {
53         DataOutput output = new DataOutput();
54         printFont(font, output, i);
55         return output.getBytes();
56     }
57 
printFont(CFFFont font, DataOutput output, String iStr)58     private void printFont(CFFFont font, DataOutput output, String iStr)
59             throws IOException {
60         output.println("%!FontType1-1.0 " + font.getName() + iStr + " "
61                 + font.getTopDict().get("version"));
62 
63         printFontDictionary(font, output, iStr);
64 
65         for (int i = 0; i < 8; i++) {
66             StringBuilder sb = new StringBuilder();
67 
68             for (int j = 0; j < 64; j++) {
69                 sb.append("0");
70             }
71 
72             output.println(sb.toString());
73         }
74 
75         output.println("cleartomark");
76     }
77 
printFontDictionary(CFFFont font, DataOutput output, String iStr)78     private void printFontDictionary(CFFFont font, DataOutput output, String iStr)
79             throws IOException {
80         output.println("10 dict begin");
81         output.println("/FontInfo 10 dict dup begin");
82         output.println("/version (" + font.getTopDict().get("version")
83                 + ") readonly def");
84         output.println("/Notice (" + font.getTopDict().get("Notice")
85                 + ") readonly def");
86         output.println("/FullName (" + font.getTopDict().get("FullName")
87                 + ") readonly def");
88         output.println("/FamilyName (" + font.getTopDict().get("FamilyName")
89                 + ") readonly def");
90         output.println("/Weight (" + font.getTopDict().get("Weight")
91                 + ") readonly def");
92         output.println("/ItalicAngle " + font.getTopDict().get("ItalicAngle")
93                 + " def");
94         output.println("/isFixedPitch " + font.getTopDict().get("isFixedPitch")
95                 + " def");
96         output.println("/UnderlinePosition "
97                 + font.getTopDict().get("UnderlinePosition") + " def");
98         output.println("/UnderlineThickness "
99                 + font.getTopDict().get("UnderlineThickness") + " def");
100         output.println("end readonly def");
101         output.println("/FontName /" + font.getName() + iStr + " def");
102         output.println("/PaintType " + font.getTopDict().get("PaintType") + " def");
103         output.println("/FontType 1 def");
104         NumberFormat matrixFormat = new DecimalFormat("0.########", new DecimalFormatSymbols(Locale.US));
105         output.println("/FontMatrix "
106                 + formatArray(font.getTopDict().get("FontMatrix"), matrixFormat, false)
107                 + " readonly def");
108         output.println("/FontBBox "
109                 + formatArray(font.getTopDict().get("FontBBox"), false)
110                 + " readonly def");
111         output.println("/StrokeWidth " + font.getTopDict().get("StrokeWidth")
112                 + " def");
113 
114         int max = 0;
115         StringBuilder sb = new StringBuilder();
116         for (Map.Entry<Integer, Integer> gid : gids.entrySet()) {
117             String name = "gid_" + gid.getKey();
118             if (gid.getKey() == 0) {
119                 name = ".notdef";
120             }
121             if (font instanceof CFFType1Font) {
122                 name = font.getCharset().getNameForGID(gid.getKey());
123             }
124             sb.append(String.format("dup %d /%s put", gid.getValue(), name)).append('\n');
125             max = Math.max(max, gid.getValue());
126         }
127         output.println("/Encoding " + (max + 1) + " array");
128         output.println("0 1 " + max + " {1 index exch /.notdef put} for");
129         output.print(sb.toString());
130         output.println("readonly def");
131 
132         output.println("currentdict end");
133 
134         DataOutput eexecOutput = new DataOutput();
135 
136         printEexecFontDictionary(font, eexecOutput);
137 
138         output.println("currentfile eexec");
139 
140         byte[] eexecBytes = Type1FontUtil.eexecEncrypt(eexecOutput.getBytes());
141         output.write(eexecBytes);
142     }
143 
printEexecFontDictionary(CFFFont font, DataOutput output)144     private void printEexecFontDictionary(CFFFont font, DataOutput output)
145             throws IOException {
146         output.println("dup /Private 15 dict dup begin");
147         output.println("/RD {string currentfile exch readstring pop} executeonly def");
148         output.println("/ND {noaccess def} executeonly def");
149         output.println("/NP {noaccess put} executeonly def");
150         Map<String, Object> privDict;
151         if (font instanceof CFFCIDFont) {
152             privDict = ((CFFCIDFont)font).getPrivDicts().get(0);
153         } else {
154             privDict = ((CFFType1Font)font).getPrivateDict();
155         }
156         output.println("/BlueValues "
157                 + formatArray(privDict.get("BlueValues"), true) + " ND");
158         output.println("/OtherBlues "
159                 + formatArray(privDict.get("OtherBlues"), true) + " ND");
160         output.println("/BlueScale " + privDict.get("BlueScale") + " def");
161         output.println("/BlueShift " + privDict.get("BlueShift") + " def");
162         output.println("/BlueFuzz " + privDict.get("BlueFuzz") + " def");
163         output.println("/StdHW " + formatArray(privDict.get("StdHW"), true)
164                 + " ND");
165         output.println("/StdVW " + formatArray(privDict.get("StdVW"), true)
166                 + " ND");
167         output.println("/ForceBold " + privDict.get("ForceBold") + " def");
168         output.println("/MinFeature {16 16} def");
169         output.println("/password 5839 def");
170 
171         output.println("2 index /CharStrings " + gids.size() + " dict dup begin");
172         Type1CharStringFormatter formatter = new Type1CharStringFormatter();
173         for (int gid : gids.keySet()) {
174             String mapping = "gid_" + gid;
175             if (gid == 0) {
176                 mapping = ".notdef";
177             }
178             byte[] type1Bytes;
179             if (font instanceof CFFCIDFont) {
180                 int cid = font.getCharset().getCIDForGID(gid);
181                 type1Bytes = formatter.format(((CFFCIDFont)font).getType2CharString(cid).getType1Sequence());
182             } else {
183                 mapping = font.getCharset().getNameForGID(gid);
184                 type1Bytes = formatter.format(((CFFType1Font)font).getType1CharString(mapping).getType1Sequence());
185             }
186             byte[] charstringBytes = Type1FontUtil.charstringEncrypt(type1Bytes, 4);
187             output.print("/" + mapping + " " + charstringBytes.length + " RD ");
188             output.write(charstringBytes);
189             output.print(" ND");
190             output.println();
191         }
192 
193         output.println("end");
194         output.println("end");
195 
196         output.println("readonly put");
197         output.println("noaccess put");
198         output.println("dup /FontName get exch definefont pop");
199         output.println("mark currentfile closefile");
200     }
201 
formatArray(Object object, boolean executable)202     private static String formatArray(Object object, boolean executable) {
203         return formatArray(object, null, executable);
204     }
205 
formatArray(Object object, NumberFormat format, boolean executable)206     private static String formatArray(Object object, NumberFormat format, boolean executable) {
207         StringBuffer sb = new StringBuffer();
208 
209         sb.append(executable ? "{" : "[");
210 
211         if (object instanceof Collection) {
212             String sep = "";
213 
214             Collection<?> elements = (Collection<?>) object;
215             for (Object element : elements) {
216                 sb.append(sep).append(formatElement(element, format));
217 
218                 sep = " ";
219             }
220         } else if (object instanceof Number) {
221             sb.append(formatElement(object, format));
222         }
223 
224         sb.append(executable ? "}" : "]");
225 
226         return sb.toString();
227     }
228 
formatElement(Object object, NumberFormat format)229     private static String formatElement(Object object, NumberFormat format) {
230         if (format != null) {
231             if (object instanceof Double || object instanceof Float) {
232                 Number number = (Number)object;
233                 return format.format(number.doubleValue());
234             } else if (object instanceof Long || object instanceof Integer) {
235                 Number number = (Number)object;
236                 return format.format(number.longValue());
237             }
238         }
239         return String.valueOf(object);
240     }
241 }
242