source: trunk/docx4j/src/main/java/org/docx4j/fonts/PhysicalFonts.java @ 1555

Revision 1555, 14.6 KB checked in by jharrop, 11 months ago (diff)

Apply Dave Brown's "assorted patches" of 28 April, as described at
 http://dev.plutext.org/forums/docx-java-f6/assorted-patches-t712.html

Line 
1package org.docx4j.fonts;
2
3import java.io.File;
4import java.net.URL;
5import java.util.HashMap;
6import java.util.Iterator;
7import java.util.List;
8import java.util.Map;
9
10import org.apache.log4j.Logger;
11import org.docx4j.fonts.fop.fonts.EmbedFontInfo;
12import org.docx4j.fonts.fop.fonts.FontCache;
13import org.docx4j.fonts.fop.fonts.FontResolver;
14import org.docx4j.fonts.fop.fonts.FontSetup;
15import org.docx4j.fonts.fop.fonts.FontTriplet;
16import org.docx4j.fonts.fop.fonts.autodetect.FontFileFinder;
17import org.docx4j.fonts.fop.fonts.autodetect.FontInfoFinder;
18import org.docx4j.fonts.microsoft.MicrosoftFonts;
19import org.docx4j.fonts.microsoft.MicrosoftFontsRegistry;
20import org.docx4j.openpackaging.parts.WordprocessingML.ObfuscatedFontPart;
21
22//import com.lowagie.text.pdf.BaseFont;
23
24/**
25 * The fonts which are physically installed on the system.
26 *
27 * They can be discovered automatically, or you can
28 * just add specific fonts.
29 *
30 * @author dev
31 *
32 */
33public class PhysicalFonts {
34
35        protected static Logger log = Logger.getLogger(PhysicalFonts.class);
36       
37        protected static FontCache fontCache;
38
39       
40        /** These are the physical fonts on the system which we have discovered. */ 
41        private final static Map<String, PhysicalFont> physicalFontMap;
42        public static Map<String, PhysicalFont> getPhysicalFonts() {
43                return physicalFontMap;
44        }
45
46        private final static Map<String, PhysicalFont> physicalFontMapByFilenameLowercase;
47       
48       
49        //      private final static Map<String, PhysicalFontFamily> physicalFontFamiliesMap;
50//      int lastSeenNumberOfPhysicalFonts = 0;
51//     
52//   
53//    /** Max difference for it to be considered an acceptable match.
54//     *  Note that this value will depend on the weights in the
55//     *  difference function.
56//     */
57//    public static final int MATCH_THRESHOLD = 30;
58
59    private static FontResolver fontResolver;       
60       
61    // parse font to ascertain font info
62    private static FontInfoFinder fontInfoFinder; 
63
64    static {
65               
66                try {
67
68                fontCache = FontCache.load();
69                if (fontCache == null) {
70                    fontCache = new FontCache();
71                }                       
72                       
73                        physicalFontMap = new HashMap<String, PhysicalFont>();
74                        physicalFontMapByFilenameLowercase
75                                                        = new HashMap<String, PhysicalFont>();
76                       
77//                      physicalFontFamiliesMap = new HashMap<String, PhysicalFontFamily>();
78                       
79                        fontResolver = FontSetup.createMinimalFontResolver();
80                       
81            // parse font to ascertain font info
82                        fontInfoFinder = new FontInfoFinder();                 
83                       
84                        // setupPhysicalFonts();
85                       
86                } catch (Exception exc) {
87                        throw new RuntimeException(exc);
88                }
89        }
90       
91        /**
92         * Autodetect fonts available on the system.
93         *
94         */ 
95        public final static void discoverPhysicalFonts() throws Exception {
96               
97                // Currently we use FOP - inspired by org.apache.fop.render.PrintRendererConfigurator
98                // iText also has a font discoverer (which we could use
99                // instead, but don't). 
100                // It remains to be seen which of XSL FO or iText
101                // PDF generation becomes the most popular.
102                // (If it is iText, then there _may_ be something
103                //  to be said for using their font discovery instead)
104               
105               
106        FontFileFinder fontFileFinder = new FontFileFinder();
107       
108        // Automagically finds a list of font files on local system
109        // based on os.name
110        List fontFileList = fontFileFinder.find();               
111        for (Iterator iter = fontFileList.iterator(); iter.hasNext();) {
112               
113                URL fontUrl = getURL(iter.next());
114           
115            // parse font to ascertain font info
116            FontInfoFinder finder = new FontInfoFinder();
117            addPhysicalFont( fontUrl);
118        }
119
120        // Add fonts from our Temporary Embedded Fonts dir
121        fontFileList = fontFileFinder.find( ObfuscatedFontPart.getTemporaryEmbeddedFontsDir() );
122        for (Iterator iter = fontFileList.iterator(); iter.hasNext();) {
123            URL fontUrl = getURL(iter.next());
124            addPhysicalFont( fontUrl);
125        }
126       
127        fontCache.save();
128       
129        }
130       
131        private static URL getURL(Object o) throws Exception {
132               
133        if (o instanceof java.io.File) {
134                // Running in Tomcat
135                java.io.File f = (java.io.File)o;
136                return f.toURL();
137        } else if (o instanceof java.net.URL) {
138                return (URL)o;
139        } else {
140                throw new Exception("Unexpected object:" + o.getClass().getName() );
141        }               
142        }
143
144        private static boolean loggedWarningAlready = false;
145       
146        /**
147         * Add a physical font's EmbedFontInfo object.
148         *
149         * @param fontUrl eg new java.net.URL("file:" + path)
150         */
151        public static void addPhysicalFont(URL fontUrl) {
152               
153                //List<EmbedFontInfo> embedFontInfoList = fontInfoFinder.find(fontUrl, fontResolver, fontCache);               
154                EmbedFontInfo[] embedFontInfoList = fontInfoFinder.find(fontUrl, fontResolver, fontCache);
155                /* FOP r644208 (Bugzilla #44737) 3/04/08 made this an array,
156                // so if you are using non-patched FOP, it needs to be at least this revision
157                // (but doesn't seem to be in FOP 0.95 binary?!) */ 
158               
159                if (embedFontInfoList==null) {
160                        // Quite a few fonts exist that we can't seem to get
161                        // EmbedFontInfo for. To be investigated.
162                        log.warn("Aborting: " + fontUrl.toString() +  " (can't get EmbedFontInfo[] .. try deleting fop-fonts.cache?)");
163                        return;
164                }
165               
166                StringBuffer debug = new StringBuffer();
167               
168                for ( EmbedFontInfo fontInfo : embedFontInfoList ) {
169                       
170                        /* EmbedFontInfo has:
171                         * - subFontName (if the underlying CustomFont is a TTC)
172                         * - PostScriptName = CustomFont.getFontName()
173                         * - FontTriplets named:
174                         *              - CustomFont.getFullName() with quotes stripped
175                         *              - CustomFont.getFontName() with whitespace stripped
176                         *              - each family name        (with quotes stripped)
177                         *
178                         * By creating one PhysicalFont object
179                         * per triplet, each referring to the same
180                         * EmbedFontInfo, we increase the chances
181                         * of a match
182                         *
183                                ComicSansMS
184                                .. triplet Comic Sans MS (priority + 0
185                                .. triplet ComicSansMS (priority + 0
186                               
187                                ComicSansMS-Bold
188                                .. triplet Comic Sans MS Bold (priority + 0
189                                .. triplet ComicSansMS-Bold (priority + 0
190                                .. triplet Comic Sans MS (priority + 5
191                         *
192                         * but the second triplet is what FOP creates where its
193                         * getPostScriptName()
194                         * does FontUtil.stripWhiteSpace(getFullName());.
195                         *
196                         * and the third is just the family name.
197                         *
198                         * So we only get the first.
199                         *
200                         */
201                       
202                       
203                        if (fontInfo == null) {
204//                              return;
205                                continue;
206                        }
207                       
208                        debug.append("------- \n");
209                       
210                         try {
211                                debug.append(fontInfo.getPostScriptName() + "\n" );
212                                if (!fontInfo.isEmbeddable() ) {                                       
213//                      log.info(tokens[x] + " is not embeddable; skipping.");
214                                         
215                                                /*
216                                                 * No point looking at this font, since if we tried to use it,
217                                                 * later, we'd get:
218                                                 * 
219                                                 * com.lowagie.text.DocumentException: file:/usr/share/fonts/truetype/ttf-tamil-fonts/lohit_ta.ttf cannot be embedded due to licensing restrictions.
220                                                        at com.lowagie.text.pdf.TrueTypeFont.<init>(TrueTypeFont.java:364)
221                                                        at com.lowagie.text.pdf.TrueTypeFont.<init>(TrueTypeFont.java:335)
222                                                        at com.lowagie.text.pdf.BaseFont.createFont(BaseFont.java:399)
223                                                        at com.lowagie.text.pdf.BaseFont.createFont(BaseFont.java:345)
224                                                        at org.xhtmlrenderer.pdf.ITextFontResolver.addFont(ITextFontResolver.java:164)
225                                                       
226                                                        will be thrown if os_2.fsType == 2
227                                                       
228                                                 */
229                                        log.warn(fontInfo.getEmbedFile() + " is not embeddable; ignoring this font.");
230                                         
231                                         //return;
232                                    continue;
233                                 }
234                        } catch (Exception e1) {
235                                // NB isEmbeddable() only exists in our patched FOP
236                                if (!loggedWarningAlready) {
237                                        log.warn("Not using patched FOP; isEmbeddable() method missing.");
238                                        loggedWarningAlready = true;
239                                }                               
240                        }
241                               
242                        PhysicalFont pf; 
243                       
244//                      for (Iterator iterIn = fontInfo.getFontTriplets().iterator() ; iterIn.hasNext();) {
245//                              FontTriplet triplet = (FontTriplet)iterIn.next();
246                       
247                                FontTriplet triplet = (FontTriplet)fontInfo.getFontTriplets().get(0); 
248                                // There is one triplet for each of the font family names
249                                // this font has, and we create a PhysicalFont object
250                                // for each of them.  For our purposes though, each of
251                                // these physical font objects contains the same info
252                       
253                        String lower = fontInfo.getEmbedFile().toLowerCase();
254                        log.debug("Processing physical font: " + lower);
255                                debug.append(".. triplet " + triplet.getName() 
256                                                + " (priority " + triplet.getPriority() +"\n" );
257                                               
258                        pf = null;
259                        // xhtmlrenderer's org.xhtmlrenderer.pdf.ITextFontResolver.addFont
260                        // can handle
261                        // .otf, .ttf, .ttc, .pfb
262                        if (lower.endsWith(".otf") || lower.endsWith(".ttf") || lower.endsWith(".ttc") ) {
263                                pf = new PhysicalFont(triplet.getName(), fontInfo, fontResolver);
264                        } else if (lower.endsWith(".pfb") ) {
265                                // See whether we have everything org.xhtmlrenderer.pdf.ITextFontResolver.addFont
266                                // will need - for a .pfb file, it needs a corresponding .afm or .pfm
267                                        String afm = FontUtils.pathFromURL(lower);
268                                        afm = afm.substring(0, afm.length()-4 ) + ".afm";  // drop the 'file:'
269                                        log.debug("Looking for: " + afm);                                       
270                                        File f = new File(afm);
271                                if (f.exists()) {
272                                       
273                                        log.debug(".. found");
274
275// Uncomment if you want to use the iText stuff in docx4j-extras                                       
276//                                      // We're only interested if this font supports UTF-8 encoding
277//                                      // since otherwise iText can't use it (at least on a
278//                                      // UTF8 encoded XHTML document)
279//                                      try {
280//                                          BaseFont bf = BaseFont.createFont(afm,
281//                                                      BaseFont.IDENTITY_H,
282//                                                                      BaseFont.NOT_EMBEDDED);
283//                                              } catch (java.io.UnsupportedEncodingException uee) {
284//                                                      log.error(afm + " does not support UTF encoding, so ignoring");
285//                                                      continue;
286//                                              } catch (Exception e) {
287//                                                      log.error(e);
288//                                                      continue;
289//                                              }
290                                        pf = new PhysicalFont(triplet.getName(),fontInfo, fontResolver);
291                                       
292                                       
293                                } else {
294                                        // Should we be doing afm first, or pfm?
295                                                String pfm = FontUtils.pathFromURL(lower);
296                                                pfm = pfm.substring(0, pfm.length()-4 ) + ".pfm";  // drop the 'file:'
297                                                log.debug("Looking for: " + pfm);
298                                                f = new File(pfm);
299                                        if (f.exists()) {                               
300                                                log.debug(".. found");
301
302                                                // Uncomment if you want to use the iText stuff in docx4j-extras                                                                                       
303//                                              // We're only interested if this font supports UTF-8 encoding
304//                                              try {
305//                                                  BaseFont bf = BaseFont.createFont(pfm,
306//                                                              BaseFont.IDENTITY_H,
307//                                                                              BaseFont.NOT_EMBEDDED);
308//                                                      } catch (java.io.UnsupportedEncodingException uee) {
309//                                                              log.error(pfm + " does not support UTF encoding, so ignoring");
310//                                                              continue;
311//                                                      } catch (Exception e) {
312//                                                              log.error(e);
313//                                                              continue;
314//                                                      }
315                                                pf = new PhysicalFont(triplet.getName(), fontInfo, fontResolver);
316                                        } else {
317                                                log.warn("Skipping " + triplet.getName() + "; couldn't find .afm or .pfm for : " + fontInfo.getEmbedFile());                                                                                           
318                                        }
319                                }
320                        } else {                       
321                                log.warn("Skipping " + triplet.getName() + "; unsupported type: " + fontInfo.getEmbedFile());                                           
322                        }
323                       
324                       
325                        if (pf!=null) {
326                               
327                                // Add it to the map
328                                physicalFontMap.put(pf.getName(), pf);
329                                log.debug("Added " + pf.getName() + " -> " + pf.getEmbeddedFile());
330                               
331                                // We also need to add it to map by filename
332                                String filename = pf.getEmbeddedFile();
333                                filename = filename.substring( filename.lastIndexOf("/")+1).toLowerCase();
334                                physicalFontMapByFilenameLowercase.put(filename, pf);
335                                log.debug("added to filename map: " + filename);
336                               
337//                              String familyName = triplet.getName();
338//                              pf.setFamilyName(familyName);
339//                             
340//                              PhysicalFontFamily pff;
341//                              if (physicalFontFamiliesMap.get(familyName)==null) {
342//                                      pff = new PhysicalFontFamily(familyName);
343//                                      physicalFontFamiliesMap.put(familyName, pff);
344//                              } else {
345//                                      pff = physicalFontFamiliesMap.get(familyName);
346//                              }
347//                              pff.addFont(pf);
348                               
349                        }
350                        }               
351               
352                log.debug(debug.toString() );
353        }
354       
355        public static PhysicalFont getBoldForm( PhysicalFont pf) {
356               
357                // look up the font in MicrosoftFontsRegistry
358                MicrosoftFonts.Font msFont = MicrosoftFontsRegistry.getMsFonts().get(pf.getName() );
359               
360                if (msFont==null) {
361                        log.warn("No entry in MicrosoftFontsRegistry for: " + pf.getName());
362                        return null;
363                }
364               
365                if (msFont.getBold()==null) {
366                        log.debug("No bold form for: " + pf.getName());
367                        return null;
368                } else {
369                       
370                        // We have to go via the file name, grrr..
371                        // since MicrosoftFonts.xml doesn't give the associate font name
372                        String filename = msFont.getBold().getFilename().toLowerCase();
373                        log.debug("Fetching: " + filename);
374                        return physicalFontMapByFilenameLowercase.get(filename);
375                }               
376        }
377       
378        public static PhysicalFont getBoldItalicForm( PhysicalFont pf) {
379               
380                // look up the font in MicrosoftFontsRegistry
381                MicrosoftFonts.Font msFont = MicrosoftFontsRegistry.getMsFonts().get(pf.getName() );
382               
383                if (msFont==null) {
384                        log.warn("No entry in MicrosoftFontsRegistry for: " + pf.getName());
385                        return null;
386                }
387               
388                if (msFont.getBolditalic()==null) {
389                        log.debug("No Bolditalic form for: " + pf.getName());
390                        return null;
391                } else {
392                       
393                        // We have to go via the file name, grrr..
394                        // since MicrosoftFonts.xml doesn't give the associate font name
395                        String filename = msFont.getBolditalic().getFilename().toLowerCase();
396                        log.debug("Fetching: " + filename);
397                        return physicalFontMapByFilenameLowercase.get(filename);
398                }               
399        }
400       
401        public static PhysicalFont getItalicForm( PhysicalFont pf) {
402               
403                // look up the font in MicrosoftFontsRegistry
404                MicrosoftFonts.Font msFont = MicrosoftFontsRegistry.getMsFonts().get(pf.getName() );
405               
406                if (msFont==null) {
407                        log.debug("No entry in MicrosoftFontsRegistry for: " + pf.getName());
408                        return null;
409                }
410               
411                if (msFont.getItalic()==null) {
412                        log.info("No italic form for: " + pf.getName());
413                        return null;
414                } else {
415                       
416                        // We have to go via the file name, grrr..
417                        // since MicrosoftFonts.xml doesn't give the associate font name
418                        String filename = msFont.getItalic().getFilename().toLowerCase();
419                        log.debug("Fetching: " + filename);
420                        return physicalFontMapByFilenameLowercase.get(filename);
421                }
422               
423        }
424
425        public static void main(String[] args) throws Exception {
426
427                discoverPhysicalFonts();
428                System.out.println("That should have listed your physical fonts (provided you have logging enabled).");
429        }
430       
431
432}
Note: See TracBrowser for help on using the repository browser.