/* * 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.ofont; import org.icepdf.core.pobjects.Name; import org.icepdf.core.pobjects.Reference; import org.icepdf.core.pobjects.Stream; import org.icepdf.core.pobjects.fonts.AFM; import org.icepdf.core.pobjects.fonts.FontDescriptor; import org.icepdf.core.util.FontUtil; import org.icepdf.core.util.Library; import java.awt.*; import java.util.HashMap; import java.util.List; import java.util.StringTokenizer; import java.util.logging.Level; import java.util.logging.Logger; /** * * */ public class Font extends org.icepdf.core.pobjects.fonts.Font { private static final Logger logger = Logger.getLogger(Font.class.toString()); public static final Name BASE_ENCODING_KEY = new Name("BaseEncoding"); public static final Name ENCODING_KEY = new Name("Encoding"); public static final Name TOUNICODE_KEY = new Name("ToUnicode"); public static final Name DIFFERENCES_KEY = new Name("Differences"); public static final Name WIDTHS_KEY = new Name("Widths"); public static final Name FIRST_CHAR_KEY = new Name("FirstChar"); public static final Name W_KEY = new Name("W"); public static final Name FONT_DESCRIPTOR_KEY = new Name("FontDescriptor"); public static final Name DESCENDANT_FONTS_KEY = new Name("DescendantFonts"); public static final Name NONE_KEY = new Name("none"); public static final Name STANDARD_ENCODING_KEY = new Name("StandardEncoding"); public static final Name MACROMAN_ENCODING_KEY = new Name("MacRomanEncoding"); public static final Name WINANSI_ENCODING_KEY = new Name("WinAnsiEncoding"); public static final Name PDF_DOC_ENCODING_KEY = new Name("PDFDocEncoding"); // A specification of the font's character encoding, if different from its // built-in encoding. The value of Encoding may be either the name of a predefined // encoding (MacRomanEncoding, MacExpertEncoding, or WinAnsi- Encoding, as // described in Appendix D) or an encoding dictionary that specifies // differences from the font's built-in encoding or from a specified predefined // encoding private Encoding encoding; // encoding name for debugging reasons; private Name encodingName; // An array of (LastChar ? FirstChar + 1) widths, each element being the // glyph width for the character code that equals FirstChar plus the array index. // For character codes outside the range FirstChar to LastChar, the value // of MissingWidth from the FontDescriptor entry for this font is used. private List widths; // widths for cid fonts, substitution specific, font files actually have // correct glyph widths. private HashMap<Integer, Float> cidWidths; // Base character mapping of 256 chars private char[] cMap; // ToUnicode CMap object stores any mapping information private CMap toUnicodeCMap; // Base 14 AFM fonts protected AFM afm; // awt font style reference, ITALIC or BOLD|ITALIC protected int style; // get list of all available fonts. private static final java.awt.Font[] fonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts(); // Array of type1 font differences based on family names. static final String type1Diff[][] = {{"Bookman-Demi", "URWBookmanL-DemiBold", "Arial"}, { "Bookman-DemiItalic", "URWBookmanL-DemiBoldItal", "Arial"}, { "Bookman-Light", "URWBookmanL-Ligh", "Arial"}, { "Bookman-LightItalic", "URWBookmanL-LighItal", "Arial"}, { "Courier", "Nimbus Mono L Regular", "Nimbus Mono L"}, { "Courier-Oblique", "Nimbus Mono L Regular Oblique", "Nimbus Mono L"}, { "Courier-Bold", "Nimbus Mono L Bold", "Nimbus Mono L"}, { "Courier-BoldOblique", "Nimbus Mono L Bold Oblique", "Nimbus Mono L"}, { "AvantGarde-Book", "URWGothicL-Book", "Arial"}, { "AvantGarde-BookOblique", "URWGothicL-BookObli", "Arial"}, { "AvantGarde-Demi", "URWGothicL-Demi", "Arial"}, { "AvantGarde-DemiOblique", "URWGothicL-DemiObli", "Arial"}, { "Helvetica", "Nimbus Sans L Regular", "Nimbus Sans L"}, { "Helvetica-Oblique", "Nimbus Sans L Regular Italic", "Nimbus Sans L"}, { "Helvetica-Bold", "Nimbus Sans L Bold", "Nimbus Sans L"}, { "Helvetica-BoldOblique", "Nimbus Sans L Bold Italic", "Nimbus Sans L"}, { "Helvetica-Narrow", "Nimbus Sans L Regular Condensed", "Nimbus Sans L"}, { "Helvetica-Narrow-Oblique", "Nimbus Sans L Regular Condensed Italic", "Nimbus Sans L"}, { "Helvetica-Narrow-Bold", "Nimbus Sans L Bold Condensed", "Nimbus Sans L"}, { "Helvetica-Narrow-BoldOblique", "Nimbus Sans L Bold Condensed Italic", "Nimbus Sans L"}, { "Helvetica-Condensed", "Nimbus Sans L Regular Condensed", "Nimbus Sans L"}, { "Helvetica-Condensed-Oblique", "Nimbus Sans L Regular Condensed Italic", "Nimbus Sans L"}, { "Helvetica-Condensed-Bold", "Nimbus Sans L Bold Condensed", "Nimbus Sans L"}, { "Helvetica-Condensed-BoldOblique", "Nimbus Sans L Bold Condensed Italic", "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", "Nimbus Roman No9 L Regular", "Nimbus Roman No9 L"}, { "Times-Italic", "Nimbus Roman No9 L Regular Italic", "Nimbus Roman No9 L"}, { "Times-Bold", "Nimbus Roman No9 L Medium", "Nimbus Roman No9 L"}, { "Times-BoldItalic", "Nimbus Roman No9 L Medium Italic", "Nimbus Roman No9 L"}, { "Symbol", "Standard Symbols L", "Standard Symbols L"}, { "ZapfChancery-MediumItalic", "URWChanceryL-MediItal", "Arial"}, { "ZapfDingbats", "Dingbats", "Dingbats"} }; public Font(Library library, HashMap entries) { super(library, entries); // initialize cMap array with base characters cMap = new char[256]; for (char i = 0; i < 256; i++) { cMap[i] = i; } // get font style value. style = FontUtil.guessAWTFontStyle(basefont); // strip font name clean ready for processing basefont = cleanFontName(basefont); // on a null type default to Type1 if (subtype == null) { subtype = new Name("Type1"); } // This should help with figuring out special symbols if (subtype.equals("Type3")) { basefont = "Symbol"; encoding = Encoding.getSymbol(); } // Setup encoding for type 1 fonts if (subtype.equals("Type1")) { // symbol if (basefont.equals("Symbol")) { encoding = Encoding.getSymbol(); } // ZapfDingbats else if (basefont.equalsIgnoreCase("ZapfDingbats") && subtype.equals("Type1")) { encoding = Encoding.getZapfDingBats(); } // check type1Diff table against base font and assign encoding of found else { for (String[] aType1Diff : type1Diff) { if (basefont.equals(aType1Diff[0])) { encodingName = STANDARD_ENCODING_KEY; encoding = Encoding.getStandard(); break; } } } } // TrueType fonts with a Symbol name get WinAnsi encoding if (subtype.equals("TrueType")) { if (basefont.equals("Symbol")) { encodingName = WINANSI_ENCODING_KEY; encoding = Encoding.getWinAnsi(); } } } /** * Initiate the Font. Retrieve any needed attributes, basically setup the * font so it can be used by the content parser. */ public synchronized void init() { // flag for initiated fonts if (inited) { return; } // re-initialize the char mapping array based on the encoding of the font if (encoding != null) { for (char i = 0; i < 256; i++) { cMap[i] = encoding.get(i); } } // ToUnicode indicates that we now have CMap stream that need to be parsed Object objectUnicode = library.getObject(entries, TOUNICODE_KEY); if (objectUnicode != null && objectUnicode instanceof Stream) { toUnicodeCMap = new CMap(library, new HashMap(), (Stream) objectUnicode); toUnicodeCMap.init(); } // Find any special encoding information, not used very often Object o = library.getObject(entries, ENCODING_KEY); if (o != null) { if (o instanceof HashMap) { HashMap encoding = (HashMap) o; setBaseEncoding(library.getName(encoding, BASE_ENCODING_KEY)); List differences = (List) library.getObject(encoding, DIFFERENCES_KEY); if (differences != null) { int c = 0; for (Object oo : differences) { if (oo instanceof Number) { c = ((Number) oo).intValue(); } else if (oo instanceof Name) { String n = oo.toString(); int c1 = Encoding.getUV(n); if (c1 == -1) { if (n.charAt(0) == 'a') { n = n.substring(1); try { c1 = Integer.parseInt(n); } catch (Exception ex) { logger.log(Level.FINE, "Error parings font differences"); } } } cMap[c] = (char) c1; c++; } } } } else if (o instanceof Name) { setBaseEncoding((Name) o); } } // An array of (LastChar ? FirstChar + 1) widths, each element being the // glyph width for the character code that equals FirstChar plus the array index. widths = (List) (library.getObject(entries, WIDTHS_KEY)); if (widths != null) { // Assigns the First character code defined in the font's Widths array o = library.getObject(entries, FIRST_CHAR_KEY); if (o != null) { firstchar = ((Number) o).intValue(); } } // check of a cid font else if (library.getObject(entries, W_KEY) != null) { // calculate CID widths cidWidths = calculateCIDWidths(); // first char is likely 1. firstchar = 0; // cid fonts are not afm... isAFMFont = false; } // afm fonts don't have widths. else { isAFMFont = false; } // Assign the font descriptor Object of = library.getObject(entries, FONT_DESCRIPTOR_KEY); if (of instanceof FontDescriptor) { fontDescriptor = (FontDescriptor) of; fontDescriptor.init(); } // If there is no FontDescriptor then we most likely have a core afm // font and we should get the matrix so that we can derive the correct // font. if (fontDescriptor == null && basefont != null) { // see if the baseFont name matches one of the AFM names Object afm = AFM.AFMs.get(basefont.toLowerCase()); if (afm != null && afm instanceof AFM) { AFM fontMetrix = (AFM) afm; // finally create a fontDescriptor based on AFM data. fontDescriptor = FontDescriptor.createDescriptor(library, fontMetrix); fontDescriptor.init(); } } // assign font name for descriptor if (fontDescriptor != null) { String name = fontDescriptor.getFontName(); if (name != null && name.length() > 0) { basefont = cleanFontName(name); } } // checking flags for set bits. if (fontDescriptor != null && (fontDescriptor.getFlags() & 64) != 0 && encoding == null) { encodingName = STANDARD_ENCODING_KEY; encoding = Encoding.getStandard(); } // this is a test to basic CIDFont support. The current font class // is not setup to deal with this type of font, however we can still // located the descendant font described by the CIDFont's data and try // and cMap the properties over to the type1 font Object descendant = library.getObject(entries, DESCENDANT_FONTS_KEY); if (descendant != null) { List tmp = (List) descendant; if (tmp.get(0) instanceof Reference) { // locate the font reference Object fontReference = library.getObject((Reference) tmp.get(0)); if (fontReference instanceof Font) { // create and initiate the font based on the stream data Font desendant = (Font) fontReference; desendant.toUnicodeCMap = this.toUnicodeCMap; desendant.init(); this.cidWidths = desendant.cidWidths; // point the DescendantFont font Descriptor to this Font object // this may help improve the display of some fonts. if (fontDescriptor == null) { fontDescriptor = desendant.fontDescriptor; String name = fontDescriptor.getFontName(); if (name != null) { basefont = cleanFontName(name); } } } } } // check to see if the the font subtype is Type 1 and see if it matches // one of the base 14 types included with the document. if (subtype.equals("Type1")) { AFM a = AFM.AFMs.get(basefont.toLowerCase()); if (a != null && a.getFontName() != null) { afm = a; } } // Create a new true type font based on the named basefont. if (subtype.equals("Type1")) { for (String[] aType1Diff : type1Diff) { if (basefont.equals(aType1Diff[0])) { java.awt.Font f = new java.awt.Font( aType1Diff[1], style, 12); if (f.getFamily().equals(aType1Diff[2])) { basefont = aType1Diff[1]; break; } } } } // font substitution found flag isFontSubstitution = true; // isAFMFont = true; // get most types of embedded fonts from here if (fontDescriptor != null && fontDescriptor.getEmbeddedFont() != null) { font = fontDescriptor.getEmbeddedFont(); isFontSubstitution = false; isAFMFont = false; } // look at all PS font names and try and find a match if (font == null && basefont != null) { // Check to see if any of the system fonts match the basefont name for (java.awt.Font font1 : fonts) { // remove white space StringTokenizer st = new StringTokenizer(font1.getPSName(), " ", false); String fontName = ""; while (st.hasMoreElements()) fontName += st.nextElement(); // if a match is found assign it as the real font if (fontName.equalsIgnoreCase(basefont)) { font = new OFont(new java.awt.Font(font1.getFamily(), style, 1)); basefont = font1.getPSName(); isFontSubstitution = true; break; } } } // look at font family name matches against system fonts if (font == null && basefont != null) { // clean the base name so that is has just the font family String fontFamily = FontUtil.guessFamily(basefont); for (java.awt.Font font1 : fonts) { // find font family match if (FontUtil.normalizeString( font1.getFamily()).equalsIgnoreCase(fontFamily)) { // create new font with font family name and style font = new OFont(new java.awt.Font(font1.getFamily(), style, 1)); basefont = font1.getFontName(); isFontSubstitution = true; break; } } } // if still null, shouldn't be, assigned the basefont name if (font == null) { try { font = new OFont(java.awt.Font.getFont(basefont, new java.awt.Font(basefont, style, 12))); basefont = font.getName(); } catch (Exception e) { if (logger.isLoggable(Level.WARNING)) { logger.warning("Error creating awt.font for: " + entries); } } } // If the font substitutions failed then we want to try and pick the proper // font family based on what the font name best matches up with none // font family font names. if all else fails use serif as it is the most' // common font. if (!isFontSubstitution && font != null && !font.getName().toLowerCase().contains(font.getFamily().toLowerCase())) { // see if we working with a sans serif font if ((font.getName().toLowerCase().contains("times new roman") || font.getName().toLowerCase().contains("timesnewroman") || font.getName().toLowerCase().contains("bodoni") || font.getName().toLowerCase().contains("garamond") || font.getName().toLowerCase().contains("minion web") || font.getName().toLowerCase().contains("stone serif") || font.getName().toLowerCase().contains("stoneserif") || font.getName().toLowerCase().contains("georgia") || font.getName().toLowerCase().contains("bitstream cyberbit"))) { font = new OFont(new java.awt.Font("serif", font.getStyle(), (int) font.getSize())); basefont = "serif"; } // see if we working with a monospaced font else if ((font.getName().toLowerCase().contains("helvetica") || font.getName().toLowerCase().contains("arial") || font.getName().toLowerCase().contains("trebuchet") || font.getName().toLowerCase().contains("avant garde gothic") || font.getName().toLowerCase().contains("avantgardegothic") || font.getName().toLowerCase().contains("verdana") || font.getName().toLowerCase().contains("univers") || font.getName().toLowerCase().contains("futura") || font.getName().toLowerCase().contains("stone sans") || font.getName().toLowerCase().contains("stonesans") || font.getName().toLowerCase().contains("gill sans") || font.getName().toLowerCase().contains("gillsans") || font.getName().toLowerCase().contains("akzidenz") || font.getName().toLowerCase().contains("grotesk"))) { font = new OFont(new java.awt.Font("sansserif", font.getStyle(), (int) font.getSize())); basefont = "sansserif"; } // see if we working with a mono spaced font else if ((font.getName().toLowerCase().contains("courier") || font.getName().toLowerCase().contains("courier new") || font.getName().toLowerCase().contains("couriernew") || font.getName().toLowerCase().contains("prestige") || font.getName().toLowerCase().contains("eversonmono") || font.getName().toLowerCase().contains("Everson Mono"))) { font = new OFont(new java.awt.Font("monospaced", font.getStyle(), (int) font.getSize())); basefont = "monospaced"; } // if all else fails go with the serif as it is the most common font family else { font = new OFont(new java.awt.Font("serif", font.getStyle(), (int) font.getSize())); basefont = "serif"; } } // finally if we have an empty font then we default to serif so that // we can try and render the character codes. if (font == null) { font = new OFont(new java.awt.Font("serif", style, 12)); basefont = "serif"; } // setup encoding and widths. setWidth(); font = font.deriveFont(encoding, toUnicodeCMap); if (logger.isLoggable(Level.FINE)) { logger.fine(name + " - " + encodingName + " " + basefont + " " + font.toString() + " " + isFontSubstitution); } inited = true; } /** * Sets the encoding of the font * * @param baseEncoding encoding name ususally MacRomanEncoding, * MacExpertEncoding, or WinAnsi- Encoding */ private void setBaseEncoding(Name baseEncoding) { if (baseEncoding == null) { encodingName = NONE_KEY; return; } encodingName = baseEncoding; if (baseEncoding.equals(STANDARD_ENCODING_KEY)) { encoding = Encoding.getStandard(); } else if (baseEncoding.equals(MACROMAN_ENCODING_KEY)) { encoding = Encoding.getMacRoman(); } else if (baseEncoding.equals(WINANSI_ENCODING_KEY)) { encoding = Encoding.getWinAnsi(); } else if (baseEncoding.equals(PDF_DOC_ENCODING_KEY)) { encoding = Encoding.getPDFDoc(); } // initiate encoding cMap. if (encoding != null) { for (char i = 0; i < 256; i++) { cMap[i] = encoding.get(i); } } } /** * String representation of the Font object. * * @return string representing Font object attributes. */ public String toString() { return "FONT= " + encodingName + " " + entries.toString(); } /** * Gets the widths of the given <code>character</code> and appends it to the * current <code>advance</code> * * @param character character to find width of * @param advance current advance of the character * @return width of specfied character. private float getWidth(int character, float advance) { character -= firstchar; if (widths != null) { if (character >= 0 && character < widths.size()) { return ((Number) widths.elementAt(character)).floatValue() / 1000f; } } // get any necessary afm widths else if (afm != null) { Float i = afm.getWidths()[(character)]; if (i != null) { return i / 1000f; } } // find any widths in the font descriptor else if (fontDescriptor != null) { if (fontDescriptor.getMissingWidth() > 0) return fontDescriptor.getMissingWidth() / 1000f; } return advance; }*/ /** * Utility method for setting the widths for a particular font given the * specified encoding. */ private void setWidth() { float missingWidth = 0; float ascent = 0.0f; float descent = 0.0f; if (fontDescriptor != null) { if (fontDescriptor.getMissingWidth() > 0) { missingWidth = fontDescriptor.getMissingWidth() / 1000f; ascent = fontDescriptor.getAscent() / 1000f; descent = fontDescriptor.getDescent() / 1000f; } } if (widths != null) { float[] newWidth = new float[256 - firstchar]; for (int i = 0, max = widths.size(); i < max; i++) { if (widths.get(i) != null) { newWidth[i] = ((Number) widths.get(i)).floatValue() / 1000f; } } font = font.deriveFont(newWidth, firstchar, missingWidth, ascent, descent, cMap); } else if (cidWidths != null) { // cidWidth are already scaled correct to .001 font = font.deriveFont(cidWidths, firstchar, missingWidth, ascent, descent, null); } else if (afm != null && isAFMFont) { font = font.deriveFont(afm.getWidths(), firstchar, missingWidth, ascent, descent, cMap); } } private String cleanFontName(String fontName) { // crystal report ecoding specific, this will have to made more // robust when more examples are found. fontName = FontUtil.removeBaseFontSubset(fontName); // strip commas from basefont name and replace with dashes if (subtype != null && (subtype.equals("Type0") || subtype.equals("Type1") || subtype.equals("MMType1") || subtype.equals("TrueType"))) { if (fontName != null) { // normalize so that java.awt.decode will work correctly fontName = fontName.replace(',', '-'); } } return fontName; } private HashMap<Integer, Float> calculateCIDWidths() { HashMap<Integer, Float> cidWidths = new HashMap<Integer, Float>(75); // get width vector Object o = library.getObject(entries, W_KEY); if (o instanceof List) { List cidWidth = (List) o; Object current; Object peek; List subWidth; int currentChar; for (int i = 0, max = cidWidth.size() - 1; i < max; i++) { current = cidWidth.get(i); peek = cidWidth.get(i + 1); // found format c[w1, w2 ... wn] if (current instanceof Integer && peek instanceof List) { // apply Unicode mapping if any currentChar = (Integer) current; subWidth = (List) peek; for (int j = 0, subMax = subWidth.size(); j < subMax; j++) { if (subWidth.get(j) instanceof Integer) { cidWidths.put(currentChar + j, (Integer) subWidth.get(j) / 1000f); } else if (subWidth.get(j) instanceof Float) { cidWidths.put(currentChar + j, (Float) subWidth.get(j) / 1000f); } } i++; } if (current instanceof Integer && peek instanceof Integer) { for (int j = (Integer) current; j <= (Integer) peek; j++) { // apply Unicode mapping if any currentChar = j; if (cidWidth.get(i + 2) instanceof Integer) { cidWidths.put(currentChar, (Integer) cidWidth.get(i + 2) / 1000f); } else if (cidWidth.get(i + 2) instanceof Float) { cidWidths.put(currentChar, (Float) cidWidth.get(i + 2) / 1000f); } } i += 2; } } } return cidWidths; } }