source: trunk/docx4j/src/main/java/org/docx4j/fonts/BestMatchingMapper.java @ 1768

Revision 1768, 29.0 KB checked in by jharrop, 8 weeks ago (diff)

Commentary from Jeromy Evans' forum post  http://www.docx4java.org/forums/docx-java-f6/bestmatchingmapper-bugs-handling-explicit-substitutions-t940.html

Line 
1/*
2 *  Copyright 2007-2008, Plutext Pty Ltd.
3 *   
4 *  This file is part of docx4j.
5
6    docx4j is licensed under the Apache License, Version 2.0 (the "License");
7    you may not use this file except in compliance with the License.
8
9    You may obtain a copy of the License at
10
11        http://www.apache.org/licenses/LICENSE-2.0
12
13    Unless required by applicable law or agreed to in writing, software
14    distributed under the License is distributed on an "AS IS" BASIS,
15    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16    See the License for the specific language governing permissions and
17    limitations under the License.
18
19 */
20package org.docx4j.fonts;
21
22import java.util.HashMap;
23import java.util.Iterator;
24import java.util.List;
25import java.util.Map;
26
27import javax.xml.bind.JAXBContext;
28import javax.xml.bind.Unmarshaller;
29
30import org.apache.commons.lang.StringUtils;
31import org.apache.log4j.Logger;
32import org.docx4j.fonts.microsoft.MicrosoftFonts;
33import org.docx4j.fonts.substitutions.FontSubstitutions;
34import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
35import org.docx4j.openpackaging.parts.WordprocessingML.FontTablePart;
36import org.docx4j.wml.Fonts;
37
38/**
39 *
40 * This mapper uses Panose to guess the physical font
41 * which is a closest fit for the font used in the
42 * document. 
43 *
44 * It is most likely to be suitable on Linux or OSX
45 * systems which don't have Microsoft's fonts installed.
46 *
47 * @author jharrop
48 *
49 */
50public class BestMatchingMapper extends Mapper {
51       
52        /*
53         * TODO
54         *   
55         * - Exclude non latin fonts from Panose match eg Segoe UI matching file:/usr/share/fonts/truetype/ttf-tamil-fonts/TAMu_Kalyani.ttf
56         *
57         * - Look at Unsupport CMap format: 6 in Sun's PDF stuff.
58         */
59       
60       
61       
62        protected static Logger log = Logger.getLogger(BestMatchingMapper.class);
63
64        public BestMatchingMapper() {
65                super();
66        }
67       
68       
69        private final static HashMap<String, MicrosoftFonts.Font> msFontsFilenames;
70        public final static Map<String, MicrosoftFonts.Font> getMsFontsFilenames() {
71                return msFontsFilenames;
72        }               
73       
74        /** The substitutions listed in FontSubstitutions.xml
75         * Will be used only if there is no panose match.  */
76        private final static Map<String, FontSubstitutions.Replace> explicitSubstitutionsMap;
77
78    /** Physical fonts remapped using the short key convention in FontSubstitutions.xml;
79     * For purpose, see comments below. */
80    private final static Map<String, PhysicalFont> physicalFontsByKey;
81
82
83        int lastSeenNumberOfPhysicalFonts = 0;
84
85   
86    /** Max difference for it to be considered an acceptable match.
87     *  Note that this value will depend on the weights in the
88     *  difference function.
89     */ 
90    public static final int MATCH_THRESHOLD = 30;
91   
92   
93       
94        static {
95               
96                try {
97                       
98                        // Microsoft Fonts
99                        // 1. On Microsoft platform, to embed in PDF output
100                        // 2. docx4all - all platforms - to populate font dropdown list
101                        msFontsFilenames = new HashMap<String, MicrosoftFonts.Font>();
102                        setupMicrosoftFontFilenames();
103
104                        PhysicalFonts.discoverPhysicalFonts();
105
106            physicalFontsByKey = new HashMap<String, PhysicalFont>();
107            generateKeysForPhysicalFonts();
108
109                        // //////////////////////////////////////////////////////////////////////////////////
110                        // Get candidate substitutions
111                        // On a non-MS platform, we need these for two things:
112                        // 1. to embed this font in the PDF output, in place of MS font
113                        // 2. in docx4all, use in editor
114                        // but it will only be used if there is no panose match
115                        explicitSubstitutionsMap = new HashMap<String, FontSubstitutions.Replace>();
116                        setupExplicitSubstitutionsMap();
117                       
118                       
119                } catch (Exception exc) {
120                        throw new RuntimeException(exc);
121                }
122        }
123       
124        /**
125         * Get Microsoft fonts
126         * We need these:
127         * 1. On Microsoft platform, to embed in PDF output
128         * 2. docx4all - all platforms - to populate font dropdown list */     
129        private final static void setupMicrosoftFontFilenames() throws Exception {
130                               
131                JAXBContext msFontsContext = JAXBContext.newInstance("org.docx4j.fonts.microsoft");             
132                Unmarshaller u = msFontsContext.createUnmarshaller();           
133                u.setEventHandler(new org.docx4j.jaxb.JaxbValidationEventHandler());
134
135                log.info("unmarshalling fonts.microsoft \n\n" );                                                                       
136                // Get the xml file
137                java.io.InputStream is = null;
138                // Works in Eclipse - note absence of leading '/'
139                is = org.docx4j.utils.ResourceUtils.getResource("org/docx4j/fonts/microsoft/MicrosoftFonts.xml");
140                                       
141                org.docx4j.fonts.microsoft.MicrosoftFonts msFonts = (org.docx4j.fonts.microsoft.MicrosoftFonts)u.unmarshal( is );
142               
143                List<MicrosoftFonts.Font> msFontsList = msFonts.getFont();
144               
145                for (MicrosoftFonts.Font font : msFontsList ) {                 
146                        msFontsFilenames.put( (font.getName()), font); // 20080318 - normalised
147                        //log.debug( "put msFontsFilenames: " + normalise(font.getName()) );
148                }
149               
150        }
151       
152    private static void generateKeysForPhysicalFonts() {
153        for (Map.Entry<String, PhysicalFont> entry : PhysicalFonts.getPhysicalFonts().entrySet()) {
154            physicalFontsByKey.put(generateFontKey(entry.getKey()), entry.getValue());
155        }
156    }
157
158    private static String generateFontKey(String fontName) {
159        return StringUtils.replaceChars(fontName.toLowerCase(), "- ", "");
160    }
161
162    private static PhysicalFont getPhysicalFontByKey(String key) {
163        return physicalFontsByKey.get(key);
164    }
165
166        /**
167         * Get candidate substitutions
168         * On a non-MS platform, we need these for two things:
169         * 1.  to embed this font in the PDF output, in place of MS font
170         * 2.  in docx4all, use in editor
171         * but it will only be used if there is no panose match.
172         *
173         * Issues with  FontSubstitutions.xml, as noted and addressed by Jeromy Evans
174         * http://www.docx4java.org/forums/docx-java-f6/bestmatchingmapper-bugs-handling-explicit-substitutions-t940.html
175         * 
176         * (1) FontSubstutitions.xml uses the lowercase whitespace and punctuation removed name of the font. If the document contains "Times New Roman" it is not matched to the equivalent replace element for "timesnewroman". Similarly "Arial" is not matched to "arial".
177         * (2) When matched, the method searching PhysicalFonts for the substitution font also uses the short key, not the proper name used by PhysicalFonts. For example, if matching "arial" to a substitute it tries to find "freesans" in PhysicalFont's map instead of "Free Sans".
178         * (3) On the system tested, the SubsFonts value is inclusive of the leading whitespace (eg. in the line above, the first token is "\n\t\tarial' instead of "arial" (seems odd that whitespace is included after unmarshalling). This means the first substitution always fails to match a font. As, by convention, the first token is usually the name of the font, this effectively means on systems where msttcorefonts are installed, the BestMatchingMapper fails to match the exact font. ie. it can't match "arial" to "arial" because the substitution is named "\n\t\tarial".
179         *
180         *  */ 
181        private final static void setupExplicitSubstitutionsMap() throws Exception {
182                               
183                JAXBContext substitutionsContext = JAXBContext.newInstance("org.docx4j.fonts.substitutions");           
184                Unmarshaller u2 = substitutionsContext.createUnmarshaller();           
185                u2.setEventHandler(new org.docx4j.jaxb.JaxbValidationEventHandler());
186
187                log.info("unmarshalling fonts.substitutions" );                                                                 
188                // Get the xml file
189                java.io.InputStream is2 = null;
190                // Works in Eclipse - note absence of leading '/'
191                is2 = org.docx4j.utils.ResourceUtils.getResource("org/docx4j/fonts/substitutions/FontSubstitutions.xml");
192                                       
193                org.docx4j.fonts.substitutions.FontSubstitutions fs = (org.docx4j.fonts.substitutions.FontSubstitutions)u2.unmarshal( is2 );
194               
195                List<FontSubstitutions.Replace> replaceList = fs.getReplace();
196
197                for (FontSubstitutions.Replace replacement : replaceList ) {
198                        explicitSubstitutionsMap.put(replacement.getName(), replacement);
199                }
200                               
201        }
202       
203       
204       
205       
206        /**
207         * Populate the fontMappings object. We make an entry for each
208         * of the documentFontNames.
209         *
210         * @param documentFontNames - the fonts used in the document
211         * @param wmlFonts - the content model for the fonts part
212         * @throws Exception
213         */
214        public void populateFontMappings(Map documentFontNames, org.docx4j.wml.Fonts wmlFonts ) throws Exception {
215                               
216                /* org.docx4j.wml.Fonts fonts is obtained as follows:
217                 *
218                 *     FontTablePart fontTablePart= wordMLPackage.getMainDocumentPart().getFontTablePart();
219                 *     org.docx4j.wml.Fonts fonts = (org.docx4j.wml.Fonts)fontTablePart.getJaxbElement();
220                 *     
221                 * If the document doesn't have a font table,
222                 *     
223                 *              org.docx4j.openpackaging.parts.WordprocessingML.FontTablePart fontTable
224                 *                      = new org.docx4j.openpackaging.parts.WordprocessingML.FontTablePart();
225                 *              fontTable.unmarshalDefaultFonts();
226                 */ 
227               
228                //  We need to make a map out of it.
229                List<Fonts.Font> fontList = wmlFonts.getFont();
230                Map<String, Fonts.Font> fontsInFontTable = new HashMap<String, Fonts.Font>();
231                for (Fonts.Font font : fontList ) {
232                        fontsInFontTable.put( (font.getName()), font );
233                }
234                       
235                log.info("\n\n Populating font mappings.");
236               
237                // Go through the font names, and determine which ones we can render!           
238                Iterator documentFontIterator = documentFontNames.entrySet().iterator();
239            while (documentFontIterator.hasNext()) {
240               
241                PhysicalFont fontMatched = null;
242               
243                Map.Entry pairs = (Map.Entry)documentFontIterator.next();
244               
245                if(pairs.getKey()==null) {
246                        log.info("Skipped null key");
247                        pairs = (Map.Entry)documentFontIterator.next();
248                }
249               
250                String documentFontName = (String)pairs.getKey();
251
252                        log.debug("\n\n" + documentFontName);
253                               
254                // Since docx4all invokes this method when opening
255                // each new document, the mapping may have been done
256                // last time.  We don't need to do it again, unless
257                // new physical fonts have been added (eg via
258                // an embedding)
259                if (fontMappings.get(documentFontName) != null ) {
260                        log.info(documentFontName + " already mapped.");
261                        if ( lastSeenNumberOfPhysicalFonts == 
262                                        PhysicalFonts.getPhysicalFonts().size() ) {
263                                // TODO - set this up properly!
264                        log.info(".. and no need to check again.");
265                        continue;
266                       
267                        // Assume bold, italic etc already mapped
268                       
269                        } else {
270                        log.info(".. but checking again, since physical fonts have changed.");
271                        }
272                }
273       
274//              boolean normalFormFound = false;
275                                       
276                        // Panose setup
277                        org.docx4j.wml.FontPanose wmlFontPanoseForDocumentFont = null;
278                        Fonts.Font font = fontsInFontTable.get(documentFontName);
279                        if (font==null) {
280                                log.error("Font " + documentFontName + "not found in font table!");
281                        } else {
282                                wmlFontPanoseForDocumentFont = font.getPanose1();
283                        }
284                        org.docx4j.fonts.foray.font.format.Panose documentFontPanose = null;
285                        if (wmlFontPanoseForDocumentFont!=null && wmlFontPanoseForDocumentFont.getVal()!=null ) {
286                                try {
287                                        documentFontPanose = org.docx4j.fonts.foray.font.format.Panose.makeInstance(wmlFontPanoseForDocumentFont.getVal() );
288                                } catch (IllegalArgumentException e) {                                 
289                                        log.error(e.getMessage());
290                                        // For example:
291                                        // Illegal Panose Array: Invalid value 10 > 8 in position 5 of [ 4 2 7 5 4 10 2 6 7 2 ]
292                                }
293                                if (documentFontPanose!=null) {
294                                        log.debug(".. " + documentFontPanose.toString() );
295                                }
296                               
297                        } else {
298                                log.debug(".. no panose info!!!");                                                                                                                     
299                        }
300                       
301                       
302                        /* What about a panose match?
303                         *
304                         * We rely on this almost exclusively at present.  It works very well, with the following exceptions:
305                         *
306                         * Garamond-Bold .. [ 2 2 8 4 3 3 7 1 8 3 ]
307                                Looking for [ 2 2 8 4 3 3 1 1 8 3 ]
308                                              ^----------- stuffs us up
309                                             
310                                             
311                         *
312                         */ 
313                // TODO - only do this for latin fonts!
314                        if (documentFontPanose==null ) {
315                                log.debug(" --> null Panose");                                                         
316                        } else {
317                                                               
318                                // Is the Panose value valid?
319                                if (log.isDebugEnabled() &&  org.docx4j.fonts.foray.font.format.Panose.validPanose(documentFontPanose.getPanoseArray())!=null) {                                                                                                               
320                                        // NB org.apache.fop.fonts.Panose only exists in our patched FOP
321                                        log.debug(documentFontName + " : " + org.docx4j.fonts.foray.font.format.Panose.validPanose(documentFontPanose.getPanoseArray()));                                       
322                                        //This is the case for 'Impact' which has
323                                        //Invalid value 9 > 8 in position 5 of 2 11 8 6 3 9 2 5 2 4
324                                }
325                               
326                                String panoseKey =  findClosestPanoseMatch(documentFontName, documentFontPanose,
327                                                        PhysicalFonts.getPhysicalFonts() , MATCH_THRESHOLD);
328                               
329                                if ( panoseKey==null) {
330                                        log.debug(documentFontName + " -->  no panose match");                                 
331                                } else {
332                               
333                                        fontMatched = PhysicalFonts.getPhysicalFonts().get(panoseKey);
334                                       
335                                        if (fontMatched!=null) {
336                                       
337                                        fontMappings.put(documentFontName, PhysicalFonts.getPhysicalFonts().get(panoseKey));
338                                        log.debug("Mapped " +  documentFontName  + " -->  " + panoseKey
339                                                        + "( "+ PhysicalFonts.getPhysicalFonts().get(panoseKey).getEmbeddedFile() );
340                                        } else {
341                                               
342                                                log.debug("font with key " + panoseKey + " doesn't exist!");
343                                        }
344
345                                        // Out of interest, is this match in font substitutions table?
346                                        FontSubstitutions.Replace rtmp
347                                                = (FontSubstitutions.Replace) explicitSubstitutionsMap.get(documentFontName);
348                                        if (rtmp!=null && rtmp.getSubstFonts()!=null) {
349                                                if (rtmp.getSubstFonts().contains(panoseKey) ) {
350                                                        log.debug("(consistent with explicit substitutes)");
351                                                } else {
352                                                        log.debug("(lucky, since this is missing from explicit substitutes)");                                                 
353                                                }
354                                               
355                                        }
356                                } 
357                                       
358//                              // However we found our match for the normal form of
359//                              // this document font, we still need to do
360//                              // bold, italic, and bolditalic?
361//
362//                              MicrosoftFonts.Font msFont = (MicrosoftFonts.Font)msFontsFilenames.get(documentFontName);
363//                             
364//                              if (msFont==null) {
365//                                      log.warn("Font not found in MicrosoftFonts.xml");
366//                                      continue;
367//                              }
368//                             
369////                            PhysicalFont fmTmp;
370////                           
371//                              org.apache.fop.fonts.Panose seekingPanose = null;
372//                              if (msFont.getBold()!=null) {
373//                                      log.debug("this font has a bold form");
374//                                      seekingPanose = documentFontPanose.getBold();
375//                                      fmTmp = getAssociatedPhysicalFont(documentFontName, panoseKey, seekingPanose);                                 
376//                                      if (fmTmp!=null) {
377//                                              fontMappings.put(documentFontName+BOLD, fmTmp);
378//                                      }
379//                              }
380//                             
381//                              fmTmp = null;
382//                              seekingPanose = null;
383//                              if (msFont.getItalic()!=null) {
384//                                      log.debug("this font has an italic form");
385//                                      seekingPanose = documentFontPanose.getItalic();
386//                                      fmTmp = getAssociatedPhysicalFont(documentFontName, panoseKey, seekingPanose);
387//                                      if (fmTmp!=null) {
388//                                              fontMappings.put(documentFontName+ITALIC, fmTmp);
389//                                      }                                               
390//                              }
391//                             
392//                              fmTmp = null;
393//                              seekingPanose = null;
394//                              if (msFont.getBolditalic()!=null) {
395//                                      log.debug("this font has a bold italic form");                                                                                         
396//                                      seekingPanose = documentFontPanose.getBold();
397//                                      seekingPanose = seekingPanose.getItalic();
398//                                      fmTmp = getAssociatedPhysicalFont(documentFontName, panoseKey, seekingPanose);
399//                                      if (fmTmp!=null) {
400//                                              fontMappings.put(documentFontName+BOLD_ITALIC, fmTmp);
401//                                      }                                               
402//                              }
403                               
404                                continue; // we're done with this document font
405                               
406                        } 
407               
408                        // Finally, try explicit font substitutions
409                        // - most likely to be useful for a font that doesn't have panose entries
410//                      if ( normalFormFound) {
411//                              continue;
412//                      }
413                       
414                        // Don't bother trying this for bold, italic if you've already
415                        // got the normal form
416                       
417                        log.debug("So try explicit font substitutions table");                                         
418                        FontSubstitutions.Replace replacement = (FontSubstitutions.Replace) explicitSubstitutionsMap
419                                        .get((generateFontKey(documentFontName)));
420                        if (replacement != null) {
421                                // log.debug( "\n" + fontName + " found." );
422                                // String subsFonts = replacement.getSubstFonts();
423
424                                // Is there anything in subsFonts we can use?
425                                String[] tokens = StringUtils.stripAll(replacement.getSubstFonts().split(";"));
426                               
427                        boolean foundMapping = false;
428                                for (int x = 0; x < tokens.length; x++) {
429                                        // log.debug(tokens[x]);
430                    fontMatched = getPhysicalFontByKey(tokens[x]);
431                                        if (fontMatched != null) {
432
433                                                String physicalFontFile = fontMatched.getEmbeddedFile();
434                                                log.debug("PDF: " + documentFontName + " --> "
435                                                                + physicalFontFile);
436                                                foundMapping = true;
437                                               
438                                                // Out of interest, does this have a Panose value?
439                                                // And what is the distance?
440                                                if (fontMatched.getPanose() == null ) {
441                                                        log.debug(".. as expected, lacking Panose");                                   
442                                                } else if (documentFontPanose!=null  ) {
443                                                        org.docx4j.fonts.foray.font.format.Panose physicalFontPanose = null;
444                                                        try {
445                                                                physicalFontPanose = org.docx4j.fonts.foray.font.format.Panose.makeInstance(fontMatched
446                                                                                                .getPanose()
447                                                                                                .getPanoseArray());
448                                                        } catch (IllegalArgumentException e) {                                 
449                                                                log.error(e.getMessage());
450                                                                // For example:
451                                                                // Illegal Panose Array: Invalid value 10 > 8 in position 5 of [ 4 2 7 5 4 10 2 6 7 2 ]
452                                                        }
453                                                       
454                                                        if (physicalFontPanose != null) {
455                                                                long pd = documentFontPanose
456                                                                                .difference(physicalFontPanose,
457                                                                                                null);
458
459                                                                if (pd >= MATCH_THRESHOLD) {
460                                                                        log
461                                                                                        .debug(".. with a panose distance exceeding threshold: "
462                                                                                                        + pd);
463                                                                } else {
464                                                                        // Sanity check
465                                                                        log
466                                                                                        .error(".. with a low panose distance (! How did we get here?) : "
467                                                                                                        + pd);
468                                                                }
469                                                        } 
470                                                }                                                                                               
471                                                break;
472                                        } else {
473                                                // log.debug("no match on token " + x + ":"
474                                                // + tokens[x]);
475                                        }       
476                                }
477                               
478                                if (!foundMapping) {
479                                        log.debug( documentFontName  + " -->  Couldn't find any of "
480                                                        + replacement.getSubstFonts());
481                                }
482
483                        } else {
484                                log.debug("Nothing in FontSubstitutions.xml for: "
485                                                + documentFontName);
486                               
487                                // TODO - add default fallback values
488                               
489                        }
490                       
491                        if (fontMatched!=null) {
492                                fontMappings.put(documentFontName, fontMatched);
493                                log.warn("Mapped " +  documentFontName  + " -->  " + fontMatched.getName() 
494                                                + "( "+ fontMatched.getEmbeddedFile() );
495                        } else {
496                                log.debug("Nothing added for: " + documentFontName);
497                        }
498                }
499               
500            lastSeenNumberOfPhysicalFonts = PhysicalFonts.getPhysicalFonts().size();
501        }
502
503        private final static int MATCH_THRESHOLD_INTRA_FAMILY = 4;
504
505        /**
506         * @param fm
507         * @param soughtPanose
508         */
509        private PhysicalFont getAssociatedPhysicalFont(String documentFontName, String orignalKey, org.docx4j.fonts.foray.font.format.Panose soughtPanose) {
510
511                log.debug("Looking for " + soughtPanose);
512               
513                String resultingPanoseKey;
514               
515//              // First try panose space restricted to this font family
516//              2009 03 22 - we don't have physicalFontFamiliesMap any more             
517//              if (orignalKey!=null) {
518//                      PhysicalFontFamily thisFamily =
519//                              physicalFontFamiliesMap.get( PhysicalFonts.getPhysicalFonts().get(orignalKey).getName() );                                     
520//                     
521//                      log.debug("Searching within family:" + thisFamily.getFamilyName() );
522//                     
523//                      resultingPanoseKey = findClosestPanoseMatch(documentFontName, soughtPanose,
524//                                      thisFamily.getPhysicalFonts(), MATCH_THRESHOLD_INTRA_FAMILY);   
525//                      if ( resultingPanoseKey!=null ) {
526//                              log.info("--> " + PhysicalFonts.getPhysicalFonts().get(resultingPanoseKey).getEmbeddedFile() );
527//                      fm.setPhysicalFont( PhysicalFonts.getPhysicalFonts().get(resultingPanoseKey) );                                                                                                 
528//                              return fm;
529//                      }  else {
530//                              log.warn("No match in immediate font family");
531//                      }
532//              } else {
533//                      log.debug("originalKey was null.");
534//              }
535               
536                // Well, that failed, so search the whole space
537               
538                //fm.setDocumentFont(documentFontName); ???
539                resultingPanoseKey = findClosestPanoseMatch(documentFontName, soughtPanose, PhysicalFonts.getPhysicalFonts(),
540                                MATCH_THRESHOLD); 
541                if ( resultingPanoseKey!=null ) {
542                        log.info("--> " + PhysicalFonts.getPhysicalFonts().get(resultingPanoseKey).getEmbeddedFile() );
543                return PhysicalFonts.getPhysicalFonts().get(resultingPanoseKey);
544                }  else {
545                        log.warn("No match in panose space");
546                        return null;
547                }
548        }
549       
550        /** Logic to search panose space for closest matching physical
551                font file.
552               
553                Returns key of matching font in physicalFontMap. */
554        private String findClosestPanoseMatch(String documentFontName, org.docx4j.fonts.foray.font.format.Panose documentFontPanose, 
555                        Map<String, PhysicalFont> physicalFontSpace, int matchThreshold) {
556               
557                // documentFontName enables us to use a name match to break a tie;
558                // otherwise it would not be required
559                String keywordToMatch = documentFontName.toLowerCase();         
560                if (documentFontName.indexOf(" ")>-1 ) {
561                        keywordToMatch = keywordToMatch.substring(0, keywordToMatch.indexOf(" "));
562                }
563               
564                String physicalFontKey = null;
565                String panoseKey = null;
566               
567                Iterator it = physicalFontSpace.entrySet().iterator();
568                long bestPanoseMatchValue = -1;         
569                String matchingPanoseString = null;
570            while (it.hasNext()) {
571                Map.Entry mapPairs = (Map.Entry)it.next();
572                                               
573                physicalFontKey = (String)mapPairs.getKey();
574                PhysicalFont physicalFont = (PhysicalFont)mapPairs.getValue();
575                               
576                if (physicalFont.getPanose() == null ) {                                       
577                        //log.info(physicalFontKey + " has no Panose data; skipping.");
578                        continue;
579                }
580                        org.docx4j.fonts.foray.font.format.Panose physicalFontPanose = null;
581                long panoseMatchValue = MATCH_THRESHOLD + 1; // inititaliase to a non-match
582                        try {
583                                physicalFontPanose = org.docx4j.fonts.foray.font.format.Panose.makeInstance(physicalFont.getPanose().getPanoseArray() );
584                        panoseMatchValue = documentFontPanose.difference(physicalFontPanose, null);
585                        } catch (IllegalArgumentException e) {                                 
586                                log.error(e.getMessage());
587                                // For example:
588                                // Illegal Panose Array: Invalid value 10 > 8 in position 5 of [ 4 2 7 5 4 10 2 6 7 2 ]
589                        }
590                       
591                        // Verdana and Tahoma have the same panose value,
592                        // and without this code, one may be used for the other
593                        // TODO - Garamond and Garamond-Italic also have the same
594                        // panose values, but this code is not smart enough to
595                        // pick the correct one.  Similar confusion between
596                        // Cambria and Cambria Math
597                        boolean trump = false;
598                        if (panoseMatchValue == bestPanoseMatchValue) {
599                                //log.debug("tie .. checking " + keywordToMatch  + " against " +  physicalFont.getName().toLowerCase());
600                                if (physicalFont.getName().toLowerCase().indexOf(keywordToMatch)>-1) {
601                                        trump = true;
602                                        log.debug("trumped previous best (which was " + panoseKey + ")");
603                                }
604                        }
605                       
606                        if (log.isDebugEnabled() ) {
607                                if ((panoseMatchValue > bestPanoseMatchValue) 
608                                                && (physicalFont.getName().toLowerCase().indexOf(keywordToMatch)>0) ) {
609                                        log.debug("Despite name match, " + physicalFont.getName() 
610                                                        + physicalFont.getPanose()
611                                                        + " is too far from " + documentFontPanose
612                                                        + " .. " + panoseMatchValue + " > " + bestPanoseMatchValue);
613                                }
614                        }
615               
616                if (trump || bestPanoseMatchValue==-1 || panoseMatchValue < bestPanoseMatchValue ) {
617                       
618                        bestPanoseMatchValue = panoseMatchValue;
619                        matchingPanoseString = physicalFont.getPanose().toString();
620                        panoseKey = physicalFontKey;
621                       
622                        //log.debug("Candidate " + panoseMatchValue + "  (" + panoseKey + ") " + matchingPanoseString);
623                       
624                        // Verdana and Tahoma seem to have the same panose value
625                        // so we can't use this optimisation
626                        //if (bestPanoseMatchValue==0) {
627                        //      // Can't do any better than this!
628                        //      continue; // this is just the inner while
629                        //}
630                } else {
631                        //log.debug("not small " + panoseMatchValue + "  " + fontInfo.getPanose().toString() );                 
632                }
633            }
634
635                if (panoseKey!=null && bestPanoseMatchValue < matchThreshold) {
636                        log.debug("MATCHED " + panoseKey + " --> " + matchingPanoseString + " distance " + bestPanoseMatchValue);                                       
637                       
638                        return panoseKey;
639                }  else {
640                        return null;
641                }
642               
643               
644               
645        }
646       
647
648//      public static class PhysicalFontFamily {
649//
650//              String familyName; // For example: Times New Roman
651//              public String getFamilyName() {
652//                      return familyName;
653//              }
654//
655//              PhysicalFontFamily(String familyName) {
656//                      this.familyName = familyName;
657//              }
658//
659//              // We want this, so that when were are searching panose space
660//              // for bold, bolditalic, italic, we can restrict the search
661//              // to this list
662//              Map<String, PhysicalFont> physicalFonts = new HashMap<String, PhysicalFont> ();
663//              void addFont(PhysicalFont physicalFont){
664//                      physicalFonts.put(physicalFont.getName(), physicalFont);
665//              }
666//             
667//              Map<String, PhysicalFont> getPhysicalFonts() {
668//                      return physicalFonts;
669//              }
670//             
671//      }
672       
673       
674        public static void main(String[] args) throws Exception {
675
676                String inputfilepath = "/home/dev/workspace/docx4j/sample-docs/Word2007-fonts.docx";
677                //String inputfilepath = "C:\\Users\\jharrop\\workspace\\docx4j\\sample-docs\\Word2007-fonts.docx";
678                //String inputfilepath = "/home/jharrop/workspace200711/docx4j-001/sample-docs/fonts-modesOfApplication.docx";
679                //String inputfilepath = "/home/jharrop/workspace200711/docx4all/sample-docs/TargetFeatureSet.docx"; //docx4all-fonts.docx";
680               
681                WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(new java.io.File(inputfilepath));
682                               
683                FontTablePart fontTablePart= wordMLPackage.getMainDocumentPart().getFontTablePart();           
684                org.docx4j.wml.Fonts fonts = (org.docx4j.wml.Fonts)fontTablePart.getJaxbElement();             
685       
686                BestMatchingMapper s = new BestMatchingMapper();
687                               
688                ///////////////
689                // Go through the FontsTable, and see what we have filenames for.
690//              for (Fonts.Font font : fontList ) {
691//                      String fontName =  font.getName();
692//                      MicrosoftFonts.Font msFontInfo = (MicrosoftFonts.Font)msFontsFilenames.get(fontName);
693//                      if (msFontInfo!=null) {
694//                              System.out.println( fontName + " at " + msFontInfo.getFilename() );                             
695//                      } else {
696//                              System.out.println( "? " + fontName );                                                         
697//                      }
698//              }
699               
700                //panoseDebugReportOnMicrosoftFonts( fonts );
701               
702                s.populateFontMappings(wordMLPackage.getMainDocumentPart().fontsInUse(), fonts );
703        }
704       
705        private static void panoseDebugReportOnPhysicalFonts( Map<String, PhysicalFont>physicalFontMap ) {
706                Iterator fontIterator = physicalFontMap.entrySet().iterator();
707            while (fontIterator.hasNext()) {
708                Map.Entry pairs = (Map.Entry)fontIterator.next();
709               
710                if(pairs.getKey()==null) {
711                        log.info("Skipped null key");
712                        if (pairs.getValue()!=null) {
713                                log.error(((PhysicalFont)pairs.getValue()).getEmbeddedFile());
714                        }
715                       
716                        if (fontIterator.hasNext() ) {
717                                pairs = (Map.Entry)fontIterator.next();
718                        } else {
719                                return;
720                        }
721                }
722               
723                String fontName = (String)pairs.getKey();
724
725                        PhysicalFont pf = (PhysicalFont)pairs.getValue();
726                       
727                        org.docx4j.fonts.foray.font.format.Panose fopPanose = pf.getPanose();
728                       
729                                if (fopPanose == null ) {
730                                        log.warn(fontName + " .. lacks Panose!");                                       
731                                } else if (fopPanose!=null ) {
732                                        log.debug(fontName + " .. " + fopPanose);
733                                }
734//                                      long pd = fopPanose.difference(nfontInfo.getPanose().getPanoseArray());
735//                                              System.out.println(".. panose distance: " + pd);                                       
736            }
737        }
738
739//      private static void panoseDebugReportOnMicrosoftFonts(org.docx4j.wml.Fonts wmlFonts ) {
740//                             
741//              List<Fonts.Font> fontList = wmlFonts.getFont();
742//              for (Fonts.Font font : fontList ) {
743//                     
744//                      org.docx4j.wml.FontPanose wmlFontPanoseForDocumentFont =
745//                              wmlFontPanoseForDocumentFont = font.getPanose1();
746//                     
747//                      org.apache.fop.fonts.Panose documentFontPanose = null;
748//                      if (wmlFontPanoseForDocumentFont!=null && wmlFontPanoseForDocumentFont.getVal()!=null ) {
749//                              try {
750//                                      documentFontPanose = org.apache.fop.fonts.Panose.makeInstance(wmlFontPanoseForDocumentFont.getVal() );
751//                                     
752//                                      System.out.println( font.getName() + documentFontPanose);
753//                                     
754//                              } catch (IllegalArgumentException e) {                                 
755//                                      log.error(e.getMessage());
756//                                      // For example:
757//                                      // Illegal Panose Array: Invalid value 10 > 8 in position 5 of [ 4 2 7 5 4 10 2 6 7 2 ]
758//                              }
759//                              //log.debug(".. " + fopPanose.toString() );                                     
760//                             
761//                      } else {
762//                              log.debug(".. no panose info!!!");                                                                                                                     
763//                      }
764//                     
765//          }
766//      }
767       
768//      private final static void setupAwtFontFamilyNames() {
769//             
770//              ////////////////////////////////////////////////////////////////////////////////////
771//              // What fonts are available to AWT
772//             
773//              java.awt.GraphicsEnvironment ge = java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment();
774//              //System.out.println(ge.getClass().getName());
775//              // sun.java2d.SunGraphicsEnvironment
776//              // on Ubuntu Gnome, sun.awt.X11GraphicsEnvironment, which extends SunGraphicsEnvironment
777//              // But X11GraphicsEnvironment source code is not available.
778//              //((sun.awt.X11GraphicsEnvironment)ge).loadFontFiles();
779//             
780//              //java.awt.Font[] geFonts = ge.getAllFonts();
781//              //String[] geFonts = ge.getAvailableFontFamilyNames();
782//              //for (int i=0; i<geFonts.length; i++) {
783//                      //System.out.println( geFonts[i] );
784//                      //awtFontFamilyNames.put(normalise(geFonts[i]), geFonts[i]);
785//         // }
786//      }
787       
788}
Note: See TracBrowser for help on using the repository browser.