/* * Copyright 2006-2017 ICEsoft Technologies Canada Corp. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an "AS * IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language * governing permissions and limitations under the License. */ package org.icepdf.core.pobjects.fonts; import org.icepdf.core.util.Defs; import org.icepdf.core.util.FontUtil; import java.awt.*; import java.io.File; import java.net.URL; import java.security.AccessControlException; import java.util.*; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; /** * <p>The <code>FontManager</code> class is responsible for finding available * fonts on the client operating system. This class will detect fonts the OS * and try and load fonts in the known locations for the particular OS. The * FontManager also does a recursive descent into base folders to try and find * more fonts which is extremely important on Linux systems.</p> * <p>It is possible to specify other directories to search for fonts via the * readSystemFonts methods extraFontPaths parameter {@link #readSystemFonts}. * Reading all of an operating systems font's can be time consuming. To help * speed up this process the method getFontProperties exports font data via a * Properties object. The font Properties object can then be saved to disk or * be read back into the FontManager via the setFontProperties method. </p> * * @since 2.0 */ public class FontManager { private static final Logger logger = Logger.getLogger(FontManager.class.toString()); // stores all font data private static List<Object[]> fontList; // stores fonts loaded from jar, these won't be cached private static List<Object[]> fontJarList; // flags for detecting font decorations private static int PLAIN = 0xF0000001; private static int BOLD = 0xF0000010; private static int ITALIC = 0xF0000100; private static int BOLD_ITALIC = 0xF0001000; // Differences for type1 fonts which match adobe core14 metrics private static final String TYPE1_FONT_DIFFS[][] = {{"Bookman-Demi", "URWBookmanL-DemiBold", "Arial"}, {"Bookman-DemiItalic", "URWBookmanL-DemiBoldItal", "Arial"}, {"Bookman-Light", "URWBookmanL-Ligh", "Arial"}, {"Bookman-LightItalic", "URWBookmanL-LighItal", "Arial"}, {"Courier", "NimbusMonL-Regu", "Nimbus Mono L", "CourierNew", "CourierNewPSMT"}, {"Courier-Oblique", "NimbusMonL-ReguObli", "Nimbus Mono L", "Courier,Italic", "CourierNew-Italic", "CourierNew,Italic", "CourierNewPS-ItalicMT"}, {"Courier-Bold", "NimbusMonL-Bold", "Nimbus Mono L", "Courier,Bold", "CourierNew,Bold", "CourierNew-Bold", "CourierNewPS-BoldMT"}, {"Courier-BoldOblique", "NimbusMonL-BoldObli", "Nimbus Mono L", "Courier,BoldItalic", "CourierNew-BoldItalic", "CourierNew,BoldItalic", "CourierNewPS-BoldItalicMT"}, {"AvantGarde-Book", "URWGothicL-Book", "Arial"}, {"AvantGarde-BookOblique", "URWGothicL-BookObli", "Arial"}, {"AvantGarde-Demi", "URWGothicL-Demi", "Arial"}, {"AvantGarde-DemiOblique", "URWGothicL-DemiObli", "Arial"}, {"Helvetica", "Helvetica", "Arial", "ArialMT", "NimbusSanL-Regu", "Nimbus Sans L"}, // {"Helvetica", "NimbusSanL-Regu", "Nimbus Sans L", "Arial", "ArialMT"}, // known problem in Phelps nfont engine {"Helvetica-Oblique", "NimbusSanL-ReguItal", "Nimbus Sans L", "Helvetica,Italic", "Helvetica-Italic", "Arial,Italic", "Arial-Italic", "Arial-ItalicMT"}, // {"Helvetica-Bold", "NimbusSanL-Bold", "Nimbus Sans L", "Helvetica-Black", "Helvetica,Bold", "Arial,Bold", "Arial-Bold", "Arial-BoldMT"}, // known problem in Phelps nfont engine {"Helvetica-Bold", "Helvetica,Bold", "Arial,Bold", "Arial-Bold", "Arial-BoldMT", "NimbusSanL-Bold", "Nimbus Sans L"}, {"Helvetica-BoldOblique", "NimbusSanL-BoldItal", "Helvetica-BlackOblique", "Nimbus Sans L", "Helvetica,BoldItalic", "Helvetica-BoldItalic", "Arial,BoldItalic", "Arial-BoldItalic", "Arial-BoldItalicMT"}, {"Helvetica-Black", "Helvetica,Bold", "Arial,Bold", "Arial-Bold", "Arial-BoldMT", "NimbusSanL-Bold", "Nimbus Sans L"}, {"Helvetica-BlackOblique", "NimbusSanL-BoldItal", "Helvetica-BlackOblique", "Nimbus Sans L", "Helvetica,BoldItalic", "Helvetica-BoldItalic", "Arial,BoldItalic", "Arial-BoldItalic", "Arial-BoldItalicMT"}, {"Helvetica-Narrow", "NimbusSanL-ReguCond", "Nimbus Sans L"}, {"Helvetica-Narrow-Oblique", "NimbusSanL-ReguCondItal", "Nimbus Sans L"}, {"Helvetica-Narrow-Bold", "NimbusSanL-BoldCond", "Nimbus Sans L"}, {"Helvetica-Narrow-BoldOblique", "NimbusSanL-BoldCondItal", "Nimbus Sans L"}, {"Helvetica-Condensed", "NimbusSanL-ReguCond", "Nimbus Sans L"}, {"Helvetica-Condensed-Oblique", "NimbusSanL-ReguCondItal", "Nimbus Sans L"}, {"Helvetica-Condensed-Bold", "NimbusSanL-BoldCond", "Nimbus Sans L"}, {"Helvetica-Condensed-BoldOblique", "NimbusSanL-BoldCondItal", "Nimbus Sans L"}, {"Palatino-Roman", "URWPalladioL-Roma", "Arial"}, {"Palatino-Italic", "URWPalladioL-Ital", "Arial"}, {"Palatino-Bold", "URWPalladioL-Bold", "Arial"}, {"Palatino-BoldItalic", "URWPalladioL-BoldItal", "Arial"}, {"NewCenturySchlbk-Roman", "CenturySchL-Roma", "Arial"}, {"NewCenturySchlbk-Italic", "CenturySchL-Ital", "Arial"}, {"NewCenturySchlbk-Bold", "CenturySchL-Bold", "Arial"}, {"NewCenturySchlbk-BoldItalic", "CenturySchL-BoldItal", "Arial"}, {"Times-Roman", "NimbusRomNo9L-Regu", "Nimbus Roman No9 L", "TimesNewRoman", "TimesNewRomanPSMT", "TimesNewRomanPS"}, {"Times-Italic", "NimbusRomNo9L-ReguItal", "Nimbus Roman No9 L", "TimesNewRoman,Italic", "TimesNewRoman-Italic", "TimesNewRomanPS-Italic", "TimesNewRomanPS-ItalicMT"}, {"Times-Bold", "NimbusRomNo9L-Medi", "Nimbus Roman No9 L", "TimesNewRoman,Bold", "TimesNewRoman-Bold", "TimesNewRomanPS-Bold", "TimesNewRomanPS-BoldMT"}, {"Times-BoldItalic", "NimbusRomNo9L-MediItal", "Nimbus Roman No9 L", "TimesNewRoman,BoldItalic", "TimesNewRoman-BoldItalic", "TimesNewRomanPS-BoldItalic", "TimesNewRomanPS-BoldItalicMT"}, {"Symbol", "StandardSymL", "Standard Symbols L"}, {"ZapfChancery-MediumItalic", "URWChanceryL-MediItal", "Arial"}, {"ZapfDingbats", "Dingbats", "Zapf-Dingbats"} }; private static final String[] JAPANESE_FONT_NAMES = { "Arial Unicode MS", "PMingLiU", "MingLiU", "MS PMincho", "MS Mincho", "Kochi Mincho", "Hiragino Mincho Pro", "KozMinPro Regular Acro", "HeiseiMin W3 Acro", "Adobe Ming Std Acro" }; private static final String[] CHINESE_SIMPLIFIED_FONT_NAMES = { "Arial Unicode MS", "PMingLiU", "MingLiU", "SimSun", "NSimSun", "Kochi Mincho", "STFangsong", "STSong Light Acro", "Adobe Song Std Acro" }; private static final String[] CHINESE_TRADITIONAL_FONT_NAMES = { "Arial Unicode MS", "PMingLiU", "MingLiU", "SimSun", "NSimSun", "Kochi Mincho", "BiauKai", "MSungStd Light Acro", "Adobe Song Std Acro" }; private static final String[] KOREAN_FONT_NAMES = { "Arial Unicode MS", "Dotum", "Gulim", "New Gulim", "GulimChe", "Batang", "BatangChe", "HYSMyeongJoStd Medium Acro", "Adobe Myungjo Std Acro", "AppleGothic", "Malgun Gothic", "UnDotum", "UnShinmun", "Baekmuk Gulim" }; /** * Java base font class, generally ${java.home}\lib\fonts. This is the base font directory that is used * for searching for system fonts. If all else fails this should be the fall back directory. */ public static String JAVA_FONT_PATH = Defs.sysProperty("java.home") + "/lib/fonts"; /** * Default search path for fonts on windows systems. */ public static List<String> WINDOWS_FONT_PATHS = Arrays.asList( // windir works for winNT and older 9X system, same as "systemroot" JAVA_FONT_PATH, System.getenv("WINDIR") + "\\Fonts"); /** * Default search path for fonts on Apple systems. */ public static List<String> MAC_FONT_PATHS = Arrays.asList( Defs.sysProperty("user.home") + "/Library/Fonts/", "/Library/Fonts/", JAVA_FONT_PATH, "/Network/Library/Fonts/", "/System/Library/Fonts/", "/System Folder/Fonts", "/usr/local/share/ghostscript/"); /** * Default search path for fonts on Linux/Unix systems. */ public static List<String> LINUX_FONT_PATHS = Arrays.asList( "/usr/share/fonts/", JAVA_FONT_PATH, "/usr/X11R6/lib/X11/fonts/", "/usr/openwin/lib/", "/usr/sfw/share/a2ps/afm/", "/usr/sfw/share/ghostscript/fonts/"); /** * Change the base font name from lucidasans which is a Java Physical Font * name. The name should be change to one of Java's logical font names: * Dialog, DialogInput, Monospaced, Serif, SansSerif. The closest logical * name that match LucidaSans is SansSerif. */ private static String baseFontName; static { baseFontName = Defs.property("org.icepdf.core.font.basefont", "lucidasans"); } // Singleton instance of class private static FontManager fontManager; /** * <p>Returns a static instance of the FontManager class.</p> * * @return instance of the FontManager. */ public static FontManager getInstance() { // make sure we have initialized the manager if (fontManager == null) { fontManager = new FontManager(); } return fontManager; } /** * <p>Initializes the fontList by reading the system fonts paths via readSystemFonts() * but only if the fontList is null or is empty. Generally the fontManager * is used with the org.icepdf.ri.util.FontPropertiesManager * * @return instance of the singleton fontManager. */ public FontManager initialize() { if (fontList == null || fontList.size() == 0) { readSystemFonts(null); } return fontManager; } /** * <p>Gets a Properties object containing font information for the operating * system which the FontManager is running on. This Properties object * can be saved to disk and read at a later time using the {@link #setFontProperties} * method.</p> * * @return Properties object containing font data information. */ public Properties getFontProperties() { Properties fontProperites; // make sure we are initialized if (fontList == null) { fontList = new ArrayList<Object[]>(); } // copy all data from fontList into the properties file fontProperites = new Properties(); Iterator fontIterator = fontList.iterator(); Object[] currentFont; String name; String family; Integer decorations; String path; // Build the properties file using the font name as the key and // the value is the family, decoration and path information // separated by the "|" character. while (fontIterator.hasNext()) { currentFont = (Object[]) fontIterator.next(); name = (String) currentFont[0]; family = (String) currentFont[1]; decorations = (Integer) currentFont[2]; path = (String) currentFont[3]; // add the new entry fontProperites.put(name, family + "|" + decorations + "|" + path); } return fontProperites; } /** * <p>Reads font data from the Properties file. All name and key data replaces * any existing font information.</p> * * @param fontProperties Properties object containing valid font information. * @throws IllegalArgumentException thrown, if there is a problem parsing the * Properties file. If thrown, the calling application should re-read * the system fonts. */ public void setFontProperties(Properties fontProperties) throws IllegalArgumentException { String errorString = "Error parsing font properties "; try { fontList = new ArrayList<Object[]>(150); Enumeration fonts = fontProperties.propertyNames(); String name; String family; Integer decorations; String path; StringTokenizer tokens; // read in font information while (fonts.hasMoreElements()) { name = (String) fonts.nextElement(); tokens = new StringTokenizer((String) fontProperties.get(name), "|"); // get family, decoration and path tokens family = tokens.nextToken(); decorations = new Integer(tokens.nextToken()); path = tokens.nextToken(); if (name != null && family != null && path != null) { fontList.add(new Object[]{name, family, decorations, path}); } else { throw new IllegalArgumentException(errorString); } } sortFontListByName(); } catch (Throwable e) { logger.log(Level.FINE, "Error setting font properties ", e); throw new IllegalArgumentException(errorString); } } /** * Clears internal font list of items. Used to clean list while constructing * a new list. */ public void clearFontList() { if (fontList != null) { fontList.clear(); } } /** * <p>Reads font from the specified array of file paths only, no . This font data is used to substitute fonts which are not * embedded inside a PDF document.</p> * * @param extraFontPaths array String object where each entry represents * a system directory path containing font programs. */ public synchronized void readFonts(String[] extraFontPaths) { readSystemFonts(extraFontPaths, true); } /** * Reads system fonts as defined in SYSTEM_FONT_PATHS plush any extra fonts paths. The reading * of system fonts can be suspended with the param skipSystemFonts. * * @param extraFontPaths optional, extra fonts path to read. * @param skipSystemFonts true to skip system fonts, extraFontsPaths should not be null if skipSystemFonts=true. */ private synchronized void readSystemFonts(String[] extraFontPaths, boolean skipSystemFonts) { // create a new font list if needed. if (fontList == null) { fontList = new ArrayList<Object[]>(150); } ArrayList<String> fontDirectories = new ArrayList<String>(); // load the appropriate font set for the OS. if (!skipSystemFonts) { String operationSystem = System.getProperty("os.name"); if (operationSystem != null) { operationSystem = operationSystem.toLowerCase(); if (operationSystem.contains("win")) { logger.finer("Detected Windows loading appropriate font paths."); fontDirectories.addAll(WINDOWS_FONT_PATHS); } else if (operationSystem.contains("mac")) { logger.finer("Detected OSX loading appropriate font paths."); fontDirectories.addAll(MAC_FONT_PATHS); } else { // must be an inix. logger.finer("Detected Unix/Linux loading appropriate font paths."); fontDirectories.addAll(LINUX_FONT_PATHS); } } } // tack on the extraFontPaths if (extraFontPaths != null) { logger.finer("Loading extraFontPaths specified by users"); fontDirectories.addAll(Arrays.asList(extraFontPaths)); } // check to make sure we have at least a few fonts. if (fontDirectories.size() == 0) { // fall back to at least a few fonts. logger.finer("No fonts specified or detected falling back to JAVA font paths."); fontDirectories.add(JAVA_FONT_PATH); } if (logger.isLoggable(Level.FINER)) { logger.finer("Starting recursive scan of specified font directories for system fonts."); } loadSystemFont(fontDirectories); } /** * <p>Searches all default system font paths and any font paths * specified by the extraFontPaths parameter, and records data about all * found fonts. This font data is used to substitute fonts which are not * embedded inside a PDF document.</p> * * @param extraFontPaths array String object where each entry represents * a system directory path containing font programs. */ public synchronized void readSystemFonts(String[] extraFontPaths) { readSystemFonts(extraFontPaths, false); } private void loadSystemFont(List<String> fontDirectories) { try { for (String fontDirectory : fontDirectories) { File directory = new File(fontDirectory); if (directory.canRead() && directory.isDirectory()) { logger.finer("looking into directory " + directory.getAbsolutePath()); // load files File[] files = directory.listFiles(); if (files != null) { List<String> dirPaths = new ArrayList<String>(); for (File file : files) { if (file.isFile()) { // load the font. evaluateFontForInsertion(file.getAbsolutePath()); } else if (file.isDirectory()) { dirPaths.add(file.getAbsolutePath()); } } // If we have some directories, then we want ot recursively descend. loadSystemFont(dirPaths); } } else if (directory.canRead() && directory.isFile()) { // load the font. evaluateFontForInsertion(directory.getAbsolutePath()); } } } catch (AccessControlException e) { logger.log(Level.WARNING, "SecurityException: failed to load fonts from directory: ", e); } catch (Throwable e) { logger.log(Level.FINE, "Failed to load fonts from directory: ", e); } } private void evaluateFontForInsertion(String fontPath) { // try loading the font FontFile font = buildFont(fontPath); // if a readable font was found if (font != null) { logger.finer("Found font file" + fontPath); // normalize name String fontName = font.getName().toLowerCase(); // Add new font data to the font list fontList.add(new Object[]{font.getName().toLowerCase(), // original PS name FontUtil.normalizeString(font.getFamily()), // family name guessFontStyle(fontName), // weight and decorations, mainly bold,italic fontPath}); // path to font on OS if (logger.isLoggable(Level.FINER)) { logger.finer("Adding system font: " + font.getName() + " " + fontPath); } } } /** * <p>Utility method for guessing a font family name from its base name.</p> * * @param name base name of font. * @return guess of the base fonts name. */ public static String guessFamily(String name) { String fam = name; int inx; // Family name usually precedes a common, ie. "Arial,BoldItalic" if ((inx = fam.indexOf(',')) > 0) fam = fam.substring(0, inx); // Family name usually precedes a dash, example "Times-Bold", if ((inx = fam.lastIndexOf('-')) > 0) fam = fam.substring(0, inx); return fam; } /** * <p>Gets all available font names on the operating system.</p> * * @return font names of all found fonts. */ public String[] getAvailableNames() { if (fontList != null) { String[] availableNames = new String[fontList.size()]; Iterator nameIterator = fontList.iterator(); Object[] fontData; for (int i = 0; nameIterator.hasNext(); i++) { fontData = (Object[]) nameIterator.next(); availableNames[i] = fontData[0].toString(); } return availableNames; } return null; } /** * <p>Gets all available font family names on the operating system.</p> * * @return font family names of all found fonts. */ public String[] getAvailableFamilies() { if (fontList != null) { String[] availableNames = new String[fontList.size()]; Iterator nameIterator = fontList.iterator(); Object[] fontData; for (int i = 0; nameIterator.hasNext(); i++) { fontData = (Object[]) nameIterator.next(); availableNames[i] = fontData[1].toString(); } return availableNames; } return null; } /** * <p>Gets all available font styles on the operating system.</p> * * @return font style names of all found fonts. */ public String[] getAvailableStyle() { if (fontList != null) { String[] availableStyles = new String[fontList.size()]; Iterator nameIterator = fontList.iterator(); Object[] fontData; int decorations; String style = ""; for (int i = 0; nameIterator.hasNext(); i++) { fontData = (Object[]) nameIterator.next(); decorations = (Integer) fontData[2]; if ((decorations & BOLD_ITALIC) == BOLD_ITALIC) { style += " BoldItalic"; } else if ((decorations & BOLD) == BOLD) { style += " Bold"; } else if ((decorations & ITALIC) == ITALIC) { style += " Italic"; } else if ((decorations & PLAIN) == PLAIN) { style += " Plain"; } availableStyles[i] = style; style = ""; } return availableStyles; } return null; } public FontFile getJapaneseInstance(String name, int fontFlags) { return getAsianInstance(fontList, name, JAPANESE_FONT_NAMES, fontFlags); } public FontFile getKoreanInstance(String name, int fontFlags) { return getAsianInstance(fontList, name, KOREAN_FONT_NAMES, fontFlags); } public FontFile getChineseTraditionalInstance(String name, int fontFlags) { return getAsianInstance(fontList, name, CHINESE_TRADITIONAL_FONT_NAMES, fontFlags); } public FontFile getChineseSimplifiedInstance(String name, int fontFlags) { return getAsianInstance(fontList, name, CHINESE_SIMPLIFIED_FONT_NAMES, fontFlags); } private FontFile getAsianInstance(List<Object[]> fontList, String name, String[] list, int flags) { if (fontList == null) { fontList = new ArrayList<Object[]>(150); } FontFile font = null; if (list != null) { // search for know list of fonts for (int i = list.length - 1; i >= 0; i--) { // try and find an instance of the name and family from the font list font = findFont(fontList, name, flags); if (font != null) { if (logger.isLoggable(Level.FINER)) { logger.finer("Font Substitution: Found Asian font: " + font.getName() + " for named font " + name); } return font; } } // lastly see if we can't a system font that matches the list names. // search for know list of fonts for (int i = list.length - 1; i >= 0; i--) { // try and find an instance of the name and family from the font list font = findFont(fontList, list[i], flags); if (font != null) { if (logger.isLoggable(Level.FINER)) { logger.finer("Font Substitution: Found Asian font: " + font.getName() + " for named font " + name); } return font; } } } return font; } /** * Reads the specified resources from the specified package. This method * is intended to aid in the packaging of fonts used for font substitution * and avoids the need to install fonts on the client operating system. * <br> * The following font resource types are supported are support: * <ul> * <li>TrueType - *.ttf, *.dfont, *.ttc</li> * <li>Type1 - *.pfa, *.pfb</li> * <li>OpenType - *.otf, *.otc</li> * </ul> * * @param fontResourcePackage package to look for the resources in. * @param resources file names of font resources to load. */ public void readFontPackage(String fontResourcePackage, List<String> resources) { if (fontJarList == null) { fontJarList = new ArrayList<Object[]>(35); } URL resourcePath; FontFile font; String fontName; for (String resourceName : resources) { // build the url and add the font to the font list. resourcePath = FontManager.class.getResource("/" + fontResourcePackage + "/" + resourceName); // try loading the font font = buildFont(resourcePath); // if a readable font was found if (font != null) { // normalize name fontName = font.getName().toLowerCase(); // Add new font data to the font list fontJarList.add(new Object[]{font.getName().toLowerCase(), // original PS name FontUtil.normalizeString(font.getFamily()), // family name guessFontStyle(fontName), // weight and decorations, mainly bold,italic resourcePath.toString()}); // path to font on OS if (logger.isLoggable(Level.FINER)) { logger.finer("Adding system font: " + font.getName() + " " + resourcePath.toString()); } } } } /** * <p>Get an instance of a NFont from the given font name and flag decoration * information.</p> * * @param name base name of font. * @param flags flags used to describe font. * @return a new instance of NFont which best approximates the font described * by the name and flags attribute. */ public FontFile getInstance(String name, int flags) { if (fontList == null) { fontList = new ArrayList<Object[]>(); } FontFile font; // try any attached jars first as they are likely controlled. if (fontJarList != null) { font = getType1Fonts(fontJarList, name, flags); if (font != null) { if (logger.isLoggable(Level.FINE)) { logger.fine("Font Substitution: Found type1 font: " + font.getName() + " for named font " + name); } return font; } } // try and find equivalent type1 font font = getType1Fonts(fontList, name, flags); if (font != null) { if (logger.isLoggable(Level.FINE)) { logger.fine("Font Substitution: Found type1 font: " + font.getName() + " for named font " + name); } return font; } // check the font name first against the jars list. if (fontJarList != null) { font = findFont(fontJarList, name, flags); if (font != null) { if (logger.isLoggable(Level.FINE)) { logger.fine("Font Substitution: Found type1 font: " + font.getName() + " for named font " + name); } return font; } } // try and find an instance of the name and family from the font list font = findFont(fontList, name, flags); if (font != null) { if (logger.isLoggable(Level.FINE)) { logger.fine("Font Substitution: Found system font: " + font.getName() + " for named font " + name); } return font; } // try and find an equivalent java font font = getCoreJavaFont(name, flags); if (font != null) { if (logger.isLoggable(Level.FINE)) { logger.fine("Font Substitution: Found java font: " + font.getName() + " for named font " + name); } return font; } // if all else fails return first font in fontList with matching style, // this should never happen, but just in case. if (fontList.size() > 0) { Object[] fontData; boolean found = false; int decorations = guessFontStyle(name); int style; // get first font that has a matching style for (int i = fontList.size() - 1; i >= 0; i--) { fontData = fontList.get(i); style = (Integer) fontData[2]; if (((decorations & BOLD_ITALIC) == BOLD_ITALIC) && ((style & BOLD_ITALIC) == BOLD_ITALIC)) { found = true; } else if (((decorations & BOLD) == BOLD) && ((style & BOLD) == BOLD)) { found = true; } else if (((decorations & ITALIC) == ITALIC) && ((style & ITALIC) == ITALIC)) { found = true; } else if (((decorations & PLAIN) == PLAIN) && ((style & PLAIN) == PLAIN)) { found = true; } if (found) { font = buildFont((String) fontData[3]); break; } } if (!found) { fontData = fontList.get(0); font = buildFont((String) fontData[3]); } if (logger.isLoggable(Level.FINE)) { logger.fine("Font Substitution: Found failed " + name + " " + font.getName()); } } if (font == null) { if (logger.isLoggable(Level.FINE)) { logger.fine("No Fonts can be found on your system. "); } } return font; } /** * Utility method for search the fontList array for an particular font name * that has the specified style. * * @param fontName font name with any decoration information still appended to name. * @param flags flags from content parser, to help guess style. * @return a valid font if found, null otherwise */ private FontFile findFont(List<Object[]> fontList, String fontName, int flags) { FontFile font = null; // references for system font list. Object[] fontData; String baseName; String familyName; String path; // normalize the fontName we are trying to find a match for int decorations = guessFontStyle(fontName); String name = FontUtil.normalizeString(fontName); int style; if (fontList != null) { for (int i = fontList.size() - 1; i >= 0; i--) { fontData = fontList.get(i); baseName = (String) fontData[0]; familyName = (String) fontData[1]; path = (String) fontData[3]; if (logger.isLoggable(Level.FINEST)) { logger.finest(baseName + " : " + familyName + " : " + name); } if (name.contains(familyName) || // familyName.contains(name) || fontName.toLowerCase().contains(baseName)) { style = (Integer) fontData[2]; boolean found = false; // ignore this font, as the cid mapping are not correct, or ther is // just look and feel issues with them. if (baseName.equals("opensymbol") || baseName.equals("starsymbol") || baseName.equals("symbolmt") || baseName.equals("arial-black") || baseName.equals("arial-blackitalic") || baseName.equals("new") // mapping issue with standard ascii, not sure why, TimesNewRomanPSMT is ok. || baseName.equals("timesnewromanps") // doesn't seem to the correct cid mapping otf version anyways. || baseName.equals("kozminpro-regular") ) { //found = false; } else if (((decorations & BOLD_ITALIC) == BOLD_ITALIC) && ((style & BOLD_ITALIC) == BOLD_ITALIC)) { found = true; } else if (((decorations & BOLD) == BOLD) && ((style & BOLD) == BOLD)) { found = true; } else if (((decorations & ITALIC) == ITALIC) && ((style & ITALIC) == ITALIC)) { found = true; } else if (((decorations & PLAIN) == PLAIN) && ((style & PLAIN) == PLAIN)) { found = true; } // symbol type fonts don't have an associated style, so // no point trying to match them based on style. else if (baseName.contains("wingdings") || baseName.contains("zapfdingbats") || baseName.contains("dingbats") || baseName.contains("symbol")) { found = true; } if (found) { if (logger.isLoggable(Level.FINER)) { logger.finer("Match Found for: " + fontName + ":" + getFontStyle(style, 0).trim() + " Substituting " + baseName + ":" + path); } font = buildFont((String) fontData[3]); // make sure the font does indeed exist if (font != null) { break; } } } } } return font; } /** * Loads a font specified by the fontpath parameter. If font path is invalid * or the file can not be loaded, null is returned. * * @param fontPath font path of font program to laod * @return a valid font if loadable, null otherwise */ private FontFile buildFont(String fontPath) { FontFile font = null; try { if (fontPath.startsWith("jar:file")) { font = buildFont(new URL(fontPath)); } else { File file = new File(fontPath); if (!file.canRead()) { return null; } font = buildFont(file); } } catch (Throwable e) { logger.log(Level.FINE, "Error reading font program.", e); } return font; } private FontFile buildFont(File fontFile) { String fontPath = fontFile.getPath(); FontFactory fontFactory = FontFactory.getInstance(); FontFile font = null; // found true type font if ((fontPath.endsWith(".ttf") || fontPath.endsWith(".TTF")) || (fontPath.endsWith(".dfont") || fontPath.endsWith(".DFONT")) || (fontPath.endsWith(".ttc") || fontPath.endsWith(".TTC"))) { font = fontFactory.createFontFile(fontFile, FontFactory.FONT_TRUE_TYPE, null); } // found Type 1 font else if ((fontPath.endsWith(".pfa") || fontPath.endsWith(".PFA")) || (fontPath.endsWith(".pfb") || fontPath.endsWith(".PFB"))) { font = fontFactory.createFontFile(fontFile, FontFactory.FONT_TYPE_1, null); } // found OpenType font else if ((fontPath.endsWith(".otf") || fontPath.endsWith(".OTF")) || (fontPath.endsWith(".otc") || fontPath.endsWith(".OTC"))) { font = fontFactory.createFontFile(fontFile, FontFactory.FONT_OPEN_TYPE, null); } return font; } private FontFile buildFont(URL fontUri) { FontFile font = null; try { String fontPath = fontUri.getPath(); FontFactory fontFactory = FontFactory.getInstance(); // found true type font if ((fontPath.endsWith(".ttf") || fontPath.endsWith(".TTF")) || (fontPath.endsWith(".dfont") || fontPath.endsWith(".DFONT")) || (fontPath.endsWith(".ttc") || fontPath.endsWith(".TTC"))) { font = fontFactory.createFontFile(fontUri, FontFactory.FONT_TRUE_TYPE, null); } // found Type 1 font else if ((fontPath.endsWith(".pfa") || fontPath.endsWith(".PFA")) || (fontPath.endsWith(".pfb") || fontPath.endsWith(".PFB"))) { font = fontFactory.createFontFile(fontUri, FontFactory.FONT_TYPE_1, null); } // found OpenType font else if ((fontPath.endsWith(".otf") || fontPath.endsWith(".OTF")) || (fontPath.endsWith(".otc") || fontPath.endsWith(".OTC"))) { font = fontFactory.createFontFile(fontUri, FontFactory.FONT_OPEN_TYPE, null); } } catch (Throwable e) { logger.log(Level.FINE, "Error reading font program.", e); } return font; } /** * Gets a NFont instance by matching against font style commonalities in the * Java Cores libraries. * * @param fontName font name to search for * @param flags style flags * @return a valid NFont if a match is found, null otherwise. */ private FontFile getCoreJavaFont(String fontName, int flags) { int decorations = guessFontStyle(fontName); fontName = FontUtil.normalizeString(fontName); FontFile font; // read font flags as it can sometimes give us hints as to serif // san sarif or a monospace font, there is more data we can pull if needed too. boolean isFixedPitch = (flags & org.icepdf.core.pobjects.fonts.Font.FONT_FLAG_FIXED_PITCH) != 0; boolean isSerif = (flags & org.icepdf.core.pobjects.fonts.Font.FONT_FLAG_SERIF) != 0; // boolean isSymbolic = (flags & org.icepdf.core.pobjects.fonts.Font.FONT_FLAG_SYMBOLIC) != 0; // boolean isNotSymbolic = (flags & org.icepdf.core.pobjects.fonts.Font.FONT_FLAG_NON_SYMBOLIC) != 0; // If no name are found then match against the core java font names // "Serif", java equivalent is "Lucida Bright" if (fontName.contains("timesnewroman") || fontName.contains("bodoni") || fontName.contains("garamond") || fontName.contains("minionweb") || fontName.contains("stoneserif") || fontName.contains("georgia") || fontName.contains("bitstreamcyberbit")) { // important, add style information font = findFont(fontList, "lucidabright-" + getFontStyle(decorations, flags), 0); } // see if we working with a monospaced font, we sub "Sans Serif", // java equivalent is "Lucida Sans" else if (fontName.contains("helvetica") || fontName.contains("arial") || fontName.contains("trebuchet") || fontName.contains("avantgardegothic") || fontName.contains("verdana") || fontName.contains("univers") || fontName.contains("futura") || fontName.contains("stonesans") || fontName.contains("gillsans") || fontName.contains("akzidenz") || fontName.contains("frutiger") || fontName.contains("grotesk")) { // important, add style information font = findFont(fontList, baseFontName + "-" + getFontStyle(decorations, flags), 0); } // see if we working with a mono spaced font "Mono Spaced" // java equivalent is "Lucida Sans Typewriter" else if (fontName.contains("courier") || fontName.contains("couriernew") || fontName.contains("prestige") || fontName.contains("eversonmono")) { // important, add style information font = findFont(fontList, baseFontName + "typewriter-" + getFontStyle(decorations, flags), 0); } // first try get the first match based on the style type and finally on failure // failure go with the serif as it is the most common font family else { if (isSerif) { font = findFont(fontList, "lucidabright-" + getFontStyle(decorations, flags), 0); } else if (isFixedPitch) { // lucidatypewriter, seems to make the font engine barf, converting to other // common fixed pitch font courier-new. font = findFont(fontList, "couriernew-" + getFontStyle(decorations, flags), 0); } else { // sans serif font = findFont(fontList, "lucidasans-" + getFontStyle(decorations, flags), 0); } } return font; } /** * Gets a NFont instance by matching against font style commonalities in the * of know type1 fonts * * @param fontName font name to search for * @param flags style flags * @return a valid NFont if a match is found, null otherwise. */ private FontFile getType1Fonts(List<Object[]> fontList, String fontName, int flags) { FontFile font = null; boolean found = false; boolean isType1Available = true; // find a match for family in the type 1 nfont table for (String[] TYPE1_FONT_DIFF : TYPE1_FONT_DIFFS) { for (String aTYPE1_FONT_DIFF : TYPE1_FONT_DIFF) { // first check to see font name matches any elements if (TYPE1_FONT_DIFF[0].contains(fontName)) { // next see if know type1 fonts are installed if (isType1Available) { font = findFont(fontList, TYPE1_FONT_DIFF[1], flags); if (font != null) { found = true; break; } else { isType1Available = false; } } // do a full search for possible matches. font = findFont(fontList, aTYPE1_FONT_DIFF, flags); if (font != null) { found = true; break; } } } // break out of second loop if (found) break; } return font; } /** * Gets a Font instance by matching against font style commonalities in the * of know type1 fonts * * @param fontName font name to search for * @return a valid AWT Font if a match is found, null otherwise. */ public java.awt.Font getType1AWTFont(String fontName, int fontSize) { java.awt.Font font = null; boolean found = false; boolean isType1Available = true; // find a match for family in the type 1 nfont table for (String[] TYPE1_FONT_DIFF : TYPE1_FONT_DIFFS) { for (String aTYPE1_FONT_DIFF : TYPE1_FONT_DIFF) { // first check to see font name matches any elements if (TYPE1_FONT_DIFF[0].contains(fontName)) { // next see if know type1 fonts are installed if (isType1Available) { font = findAWTFont(TYPE1_FONT_DIFF[1]); if (font != null) { found = true; break; } else { isType1Available = false; } } // do a full search for possible matches. font = findAWTFont(aTYPE1_FONT_DIFF); if (font != null) { found = true; break; } } } // break out of second loop if (found) break; } if (font != null) { font = font.deriveFont((float) fontSize); } return font; } /** * Utility method for search the fontList array for an particular font name * that has the specified style. * * @param fontName font name with any decoration information still appended to name. * @return a valid font if found, null otherwise */ private java.awt.Font findAWTFont(String fontName) { java.awt.Font font = null; // get a list of all the fonts AWT has java.awt.Font[] awtFonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts(); if (awtFonts != null) { for (java.awt.Font awtFont : awtFonts) { if (awtFont.getName().equals(fontName) || awtFont.getFamily().equals(fontName)) { return awtFont; } } } return font; } /** * Utility method which maps know style strings to an integer value which * is used later for efficient font searching. * todo: move out to FontUtil and use awt constants * * @param name base name of font. * @return integer representing dffs */ private static int guessFontStyle(String name) { name = name.toLowerCase(); int decorations = 0; if ((name.indexOf("boldital") > 0) || (name.indexOf("demiital") > 0)) { decorations |= BOLD_ITALIC; } else if (name.indexOf("bold") > 0 || name.indexOf("black") > 0 || name.endsWith("bt") || name.indexOf("demi") > 0) { decorations |= BOLD; } else if (name.indexOf("ital") > 0 || name.indexOf("obli") > 0) { decorations |= ITALIC; } else { decorations |= PLAIN; } return decorations; } /** * Returns the string representation of a font style specified by the * decoration and flags integers. * * @param sytle style specified by known offsets * @param flags flags from pdf dictionary * @return string representation of styles specified by the two integers. */ private String getFontStyle(int sytle, int flags) { // Get any useful data from the flags integer. String style = ""; if ((sytle & BOLD_ITALIC) == BOLD_ITALIC) { style += " BoldItalic"; } else if ((sytle & BOLD) == BOLD || (flags & org.icepdf.core.pobjects.fonts.Font.FONT_FLAG_FORCE_BOLD) != 0) { style += " Bold"; } else if ((sytle & ITALIC) == ITALIC || (flags & org.icepdf.core.pobjects.fonts.Font.FONT_FLAG_FORCE_BOLD) != 0) { style += " Italic"; } else if ((sytle & PLAIN) == PLAIN) { style += " Plain"; } return style; } /** * Sorts the fontList of system fonts by font name or the first element * int the object[] store. */ private static void sortFontListByName() { Collections.sort(fontList, new Comparator<Object[]>() { public int compare(Object[] o1, Object[] o2) { return ((String) o2[0]).compareTo((String) o1[0]); } }); } }