/* * 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 java.io.*; import java.util.HashMap; import java.util.StringTokenizer; import java.util.logging.Level; import java.util.logging.Logger; /** * This class parses and stores data on the 14 PostScript AFM files. * * @since 1.0 */ public class AFM { private static final Logger logger = Logger.getLogger(AFM.class.toString()); public static final int COURIER = 0; public static final int COURIER_BOLD = 1; public static final int COURIER_OBLIQUE = 2; public static final int COURIER_BOLD_OBLIQUE = 3; public static final int HELVETICA = 4; public static final int HELVETICA_BOLD = 5; public static final int HELVETICA_OBLIQUE = 6; public static final int HELVETICA_BOLD_OBLIQUE = 7; public static final int TIMES_ROMAN = 8; public static final int TIMES_BOLD = 9; public static final int TIMES_ITALIC = 10; public static final int TIMES_BOLD_ITALIC = 11; public static final int ZAPF_DINGBATS = 12; public static final int SYMBOL = 13; public static String[] AFMnames = { "Courier.afm", "Courier-Bold.afm", "Courier-Oblique.afm", "Courier-BoldOblique.afm", "Helvetica.afm", "Helvetica-Bold.afm", "Helvetica-Oblique.afm", "Helvetica-BoldOblique.afm", "Times-Roman.afm", "Times-Bold.afm", "Times-Italic.afm", "Times-BoldItalic.afm", "ZapfDingbats.afm", "Symbol.afm" }; /** * <p>The value of the <b>Flags</b> entry in a font descriptor is an * unsized 32-bit integer containg flags specifying various characteristics * of the font.</p> * <table border="1" cellpadding="1" summary=""> * <tr> * <td><b>Bit Position</b></td> * <td><b>Name</b></td> * <td><b>Meaning</b></td> * </tr> * <tr> * <td>1</td> * <td>FixedPitch</td> * <td>All glyphs have the same width (as opposed to proportional or * variable-pitch fonts, which have different widths).</td> * </tr> * <tr> * <td>2</td> * <td>Serif</td> * <td>Glyphs have serifs, which are short strokes drawn at an angle on * the top and bottom of glyph stems. ( Sans serif fonts do not have serifs.)</td> * </tr> * <tr> * <td>3</td> * <td>Symbolic</td> * <td>Font contains glyphs outside the Adobe standard Latin character * set. This flag and the Nonsymbolic flag cannot both be set or both be clear.</td> * </tr> * <tr> * <td>4</td> * <td>Script</td> * <td>Glyphs resemble cursive handwriting.</td> * </tr> * <tr> * <td>6</td> * <td>Nonsymbolic</td> * <td>Font uses the Adobe standard Latin character set or a subset of it.</td> * </tr> * <tr> * <td>7</td> * <td>Italic</td> * <td>Glyphs have dominant vertical strokes that are slanted.</td> * </tr> * <tr> * <td>17</td> * <td>AllCap</td> * <td>Font contains no lowercase letters; typically used for display * purposes, such as for titles or headlines.</td> * </tr> * <tr> * <td>18</td> * <td>SmallCap</td> * <td>Font contains both uppercase and lowercase letters. The uppercase * letters are similar to those in the regular version of the same * typeface family. The glyphs for the lowercase letters have the same * shapes as the corresponding uppercase letters, but they are sized * and their proportions adjusted so that they have the same size and * stroke weight as lowercase glyphs in the same typeface family.</td> * </tr> * <tr> * <td>19</td> * <td>ForceBold</td> * <td></td> * </tr> * </table> * Bit Position name Meaning */ private static int[] AFMFlags = { 35, // 0x100011 "Courier.afm", 35, // 0x100011 "Courier-Bold.afm", 99, // 0x1100011 "Courier-Oblique.afm", 99, // 0x1100011 "Courier-BoldOblique.afm", // 32, // 0x100000 "Helvetica.afm", 32, // 0x100000 "Helvetica-Bold.afm", 96, // 0x1100000 "Helvetica-Oblique.afm", 96, // 0x1100000 "Helvetica-BoldOblique.afm", // 34, // 0x100010 "Times-Roman.afm", 34, // 0x100010 "Times-Bold.afm", 98, // 0x1100010 "Times-Italic.afm", 98, // 0x1100010 "Times-BoldItalic.afm", // 4, // 0x100 "ZapfDingbats.afm", 4 // 0x100 "Symbol.afm" }; public static final HashMap<String, AFM> AFMs = new HashMap<String, AFM>(14); private String fontName; private String familyName; private String fullName; private float[] widths = new float[255]; private int[] fontBBox = new int[4]; private float italicAngle = 0; private float maxWidth = 0; private int avgWidth = 0; private int flags = 0; /** * Reader and parse all the core 14 AFM font descriptors */ static { try { for (int i = 0; i < AFMnames.length; i++) { AFM afm = AFM.loadFont("afm/" + AFMnames[i]); if (afm != null) { afm.setFlags(AFMFlags[i]); AFMs.put(afm.fontName.toLowerCase(), afm); } } } catch (Exception ex) { logger.log(Level.WARNING, "Error load AFM CMap files", ex); } } /** * Creates a new AFM file based on the * * @param resource name of desired resource. * @throws IOException if the specified resource could not be found or o * pened. */ public static AFM loadFont(String resource) throws IOException { InputStream in = AFM.class.getResourceAsStream(resource); if (in != null) { AFM afm = new AFM(); afm.parse(new InputStreamReader(in)); return afm; } else { if (logger.isLoggable(Level.WARNING)) { logger.warning("Could not find AFM File: " + resource); } return null; } } private AFM() { } public String getFontName() { return fontName; } public String getFullName() { return fullName; } public String getFamilyName() { return familyName; } public int[] getFontBBox() { return fontBBox; } public float getItalicAngle() { return italicAngle; } public float[] getWidths() { return widths; } public float getMaxWidth() { return maxWidth; } public int getAvgWidth() { return avgWidth; } public int getFlags() { return flags; } private void setFlags(int value) { flags = value; } /** * Utility class for parsing the contents of the care AFM files. * * @param i stream to read * @throws java.io.IOException if the reader can not find the specified * afm file. */ private void parse(Reader i) throws IOException { BufferedReader r = new BufferedReader(i); String s; int count = 0; avgWidth = 0; maxWidth = 0; while ((s = r.readLine()) != null) { StringTokenizer st = new StringTokenizer(s, " ;\t\n\r\f"); String s1 = st.nextToken(); if (s1.equalsIgnoreCase("FontName")) { fontName = st.nextToken(); } else if (s1.equalsIgnoreCase("FullName")) { fullName = st.nextToken(); } else if (s1.equalsIgnoreCase("FamilyName")) { familyName = st.nextToken(); } else if (s1.equalsIgnoreCase("FontBBox")) { fontBBox[0] = new Integer(st.nextToken()); fontBBox[1] = new Integer(st.nextToken()); fontBBox[2] = new Integer(st.nextToken()); fontBBox[3] = new Integer(st.nextToken()); } else if (s1.equalsIgnoreCase("ItalicAngle")) { italicAngle = new Float(st.nextToken()); } // font width data else if (s1.equalsIgnoreCase("C")) { int c = Integer.parseInt(st.nextToken()); while (!st.nextToken().equals("WX")) ; float wx = Integer.parseInt(st.nextToken()) / 1000f; if (c >= 0 && c < 255) { widths[count] = wx; // update max if (wx > maxWidth) { maxWidth = wx; } // update average avgWidth += wx; count++; } } } // finalized average avgWidth = avgWidth / count; } }