/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */

/* $Id: PFMFile.java 1758773 2016-09-01 13:02:29Z ssteiner $ */

package org.apache.fop.fonts.type1;

// Java
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.apache.xmlgraphics.fonts.Glyphs;

This class represents a PFM file (or parts of it) as a Java object.
/** * This class represents a PFM file (or parts of it) as a Java object. */
public class PFMFile { // Header stuff private String windowsName; private String postscriptName; private short dfItalic; //private int dfWeight; private short dfCharSet; private short dfPitchAndFamily; private int dfAvgWidth; private int dfMaxWidth; private int dfMinWidth; private short dfFirstChar; private short dfLastChar; // Extension stuff // --- // Extend Text Metrics private int etmCapHeight; private int etmXHeight; private int etmLowerCaseAscent; private int etmLowerCaseDescent; // Extent table private int[] extentTable; private Map<Integer, Map<Integer, Integer>> kerningTab = new HashMap<Integer, Map<Integer, Integer>>();
logging instance
/** * logging instance */
protected Log log = LogFactory.getLog(PFMFile.class);
Parses a PFM file
Params:
  • inStream – The stream from which to read the PFM file.
Throws:
/** * Parses a PFM file * * @param inStream The stream from which to read the PFM file. * @throws IOException In case of an I/O problem */
public void load(InputStream inStream) throws IOException { byte[] pfmBytes = IOUtils.toByteArray(inStream); InputStream bufin = inStream; bufin = new ByteArrayInputStream(pfmBytes); PFMInputStream in = new PFMInputStream(bufin); bufin.mark(512); short sh1 = in.readByte(); short sh2 = in.readByte(); if (sh1 == 128 && sh2 == 1) { //Found the first section header of a PFB file! IOUtils.closeQuietly(in); throw new IOException("Cannot parse PFM file. You probably specified the PFB file" + " of a Type 1 font as parameter instead of the PFM."); } bufin.reset(); byte[] b = new byte[16]; if ((bufin.read(b) == b.length) && new String(b, "US-ASCII").equalsIgnoreCase("StartFontMetrics")) { //Found the header of a AFM file! IOUtils.closeQuietly(in); throw new IOException("Cannot parse PFM file. You probably specified the AFM file" + " of a Type 1 font as parameter instead of the PFM."); } bufin.reset(); final int version = in.readShort(); if (version != 256) { log.warn("PFM version expected to be '256' but got '" + version + "'." + " Please make sure you specify the PFM as parameter" + " and not the PFB or the AFM."); } //final long filesize = in.readInt(); bufin.reset(); loadHeader(in); loadExtension(in); }
Parses the header of the PFM file.
Params:
  • inStream – The stream from which to read the PFM file.
Throws:
/** * Parses the header of the PFM file. * * @param inStream The stream from which to read the PFM file. * @throws IOException In case of an I/O problem */
private void loadHeader(PFMInputStream inStream) throws IOException { if (inStream.skip(80) != 80) { throw new IOException("premature EOF when skipping 80 bytes"); } dfItalic = inStream.readByte(); if (inStream.skip(2) != 2) { throw new IOException("premature EOF when skipping 2 bytes"); } inStream.readShort(); // dfWeight = dfCharSet = inStream.readByte(); if (inStream.skip(4) != 4) { throw new IOException("premature EOF when skipping 4 bytes"); } dfPitchAndFamily = inStream.readByte(); dfAvgWidth = inStream.readShort(); dfMaxWidth = inStream.readShort(); dfFirstChar = inStream.readByte(); dfLastChar = inStream.readByte(); if (inStream.skip(8) != 8) { throw new IOException("premature EOF when skipping 8 bytes"); } long faceOffset = inStream.readInt(); inStream.reset(); if (inStream.skip(faceOffset) != faceOffset) { throw new IOException("premature EOF when skipping faceOffset bytes"); } windowsName = inStream.readString(); inStream.reset(); if (inStream.skip(117) != 117) { throw new IOException("premature EOF when skipping 117 bytes"); } }
Parses the extension part of the PFM file.
Params:
  • inStream – The stream from which to read the PFM file.
/** * Parses the extension part of the PFM file. * * @param inStream The stream from which to read the PFM file. */
private void loadExtension(PFMInputStream inStream) throws IOException { final int size = inStream.readShort(); if (size != 30) { log.warn("Size of extension block was expected to be " + "30 bytes, but was " + size + " bytes."); } final long extMetricsOffset = inStream.readInt(); final long extentTableOffset = inStream.readInt(); if (inStream.skip(4) != 4) { //Skip dfOriginTable throw new IOException("premature EOF when skipping dfOriginTable bytes"); } final long kernPairOffset = inStream.readInt(); if (inStream.skip(4) != 4) { //Skip dfTrackKernTable throw new IOException("premature EOF when skipping dfTrackKernTable bytes"); } long driverInfoOffset = inStream.readInt(); if (kernPairOffset > 0) { inStream.reset(); if (inStream.skip(kernPairOffset) != kernPairOffset) { throw new IOException("premature EOF when skipping kernPairOffset bytes"); } loadKernPairs(inStream); } inStream.reset(); if (inStream.skip(driverInfoOffset) != driverInfoOffset) { throw new IOException("premature EOF when skipping driverInfoOffset bytes"); } postscriptName = inStream.readString(); if (extMetricsOffset != 0) { inStream.reset(); if (inStream.skip(extMetricsOffset) != extMetricsOffset) { throw new IOException("premature EOF when skipping extMetricsOffset bytes"); } loadExtMetrics(inStream); } if (extentTableOffset != 0) { inStream.reset(); if (inStream.skip(extentTableOffset) != extentTableOffset) { throw new IOException("premature EOF when skipping extentTableOffset bytes"); } loadExtentTable(inStream); } }
Parses the kernPairs part of the pfm file
Params:
  • inStream – The stream from which to read the PFM file.
/** * Parses the kernPairs part of the pfm file * * @param inStream The stream from which to read the PFM file. */
private void loadKernPairs(PFMInputStream inStream) throws IOException { int i = inStream.readShort(); if (log.isTraceEnabled()) { log.trace(i + " kerning pairs"); } while (i > 0) { int g1 = (int) inStream.readByte(); i--; int g2 = (int) inStream.readByte(); int adj = inStream.readShort(); if (adj > 0x8000) { adj = -(0x10000 - adj); } if (log.isTraceEnabled()) { log.trace("Char no: (" + g1 + ", " + g2 + ") kern: " + adj); final String glyph1 = Glyphs.TEX8R_GLYPH_NAMES[g1]; final String glyph2 = Glyphs.TEX8R_GLYPH_NAMES[g2]; log.trace("glyphs: " + glyph1 + ", " + glyph2); } Map<Integer, Integer> adjTab = kerningTab.get(g1); if (adjTab == null) { adjTab = new HashMap<Integer, Integer>(); } adjTab.put(g2, adj); kerningTab.put(g1, adjTab); } }
Parses the extended metrics part of the PFM file.
Params:
  • inStream – The stream from which to read the PFM file.
/** * Parses the extended metrics part of the PFM file. * * @param inStream The stream from which to read the PFM file. */
private void loadExtMetrics(PFMInputStream inStream) throws IOException { final int size = inStream.readShort(); if (size != 52) { log.warn("Size of extension block was expected to be " + "52 bytes, but was " + size + " bytes."); } if (inStream.skip(12) != 12) { //Skip etmPointSize, etmOrientation, etmMasterHeight, //etmMinScale, etmMaxScale, emtMasterUnits throw new IOException("premature EOF when skipping etmPointSize, ... bytes"); } etmCapHeight = inStream.readShort(); etmXHeight = inStream.readShort(); etmLowerCaseAscent = inStream.readShort(); etmLowerCaseDescent = -(inStream.readShort()); //Ignore the rest of the values }
Parses the extent table of the PFM file.
Params:
  • inStream – The stream from which to read the PFM file.
/** * Parses the extent table of the PFM file. * * @param inStream The stream from which to read the PFM file. */
private void loadExtentTable(PFMInputStream inStream) throws IOException { extentTable = new int[dfLastChar - dfFirstChar + 1]; dfMinWidth = dfMaxWidth; for (short i = dfFirstChar; i <= dfLastChar; i++) { extentTable[i - dfFirstChar] = inStream.readShort(); if (extentTable[i - dfFirstChar] < dfMinWidth) { dfMinWidth = extentTable[i - dfFirstChar]; } } }
Returns the Windows name of the font.
Returns:The Windows name.
/** * Returns the Windows name of the font. * * @return The Windows name. */
public String getWindowsName() { return windowsName; }
Return the kerning table. The kerning table is a Map with strings with glyphnames as keys, containing Maps as value. The value map contains a glyph name string key and an Integer value
Returns:A Map containing the kerning table
/** * Return the kerning table. The kerning table is a Map with * strings with glyphnames as keys, containing Maps as value. * The value map contains a glyph name string key and an Integer value * * @return A Map containing the kerning table */
public Map<Integer, Map<Integer, Integer>> getKerning() { return kerningTab; }
Returns the Postscript name of the font.
Returns:The Postscript name.
/** * Returns the Postscript name of the font. * * @return The Postscript name. */
public String getPostscriptName() { return postscriptName; }
Returns the charset used for the font.
Returns:The charset (0=WinAnsi).
/** * Returns the charset used for the font. * * @return The charset (0=WinAnsi). */
public short getCharSet() { return dfCharSet; }
Returns the charset of the font as a string.
Returns:The name of the charset.
/** * Returns the charset of the font as a string. * * @return The name of the charset. */
public String getCharSetName() { //TODO Had to remove the detection for Expert(Subset) encoding. The PFM is not suitable //for detecting these character sets. We have to parse the AFM for that. switch (dfCharSet) { case 0: return "WinAnsi"; // AKA ISOAdobe case 2: if ("Symbol".equals(getPostscriptName())) { return "Symbol"; } break; case 128: return "Shift-JIS (Japanese)"; default: log.warn("Unknown charset detected (" + dfCharSet + ", 0x" + Integer.toHexString(dfCharSet) + "). Trying fallback to WinAnsi."); } return "WinAnsi"; }
Returns the number of the character that defines the first entry in the widths list.
Returns:The number of the first character.
/** * Returns the number of the character that defines * the first entry in the widths list. * * @return The number of the first character. */
public short getFirstChar() { return dfFirstChar; }
Returns the number of the character that defines the last entry in the widths list.
Returns:The number of the last character.
/** * Returns the number of the character that defines * the last entry in the widths list. * * @return The number of the last character. */
public short getLastChar() { return dfLastChar; }
Returns the CapHeight parameter for the font (height of uppercase H).
Returns:The CapHeight parameter.
/** * Returns the CapHeight parameter for the font (height of uppercase H). * * @return The CapHeight parameter. */
public int getCapHeight() { return etmCapHeight; }
Returns the XHeight parameter for the font (height of lowercase x).
Returns:The CapHeight parameter.
/** * Returns the XHeight parameter for the font (height of lowercase x). * * @return The CapHeight parameter. */
public int getXHeight() { return etmXHeight; }
Returns the LowerCaseAscent parameter for the font (height of lowercase d).
Returns:The LowerCaseAscent parameter.
/** * Returns the LowerCaseAscent parameter for the font (height of lowercase d). * * @return The LowerCaseAscent parameter. */
public int getLowerCaseAscent() { return etmLowerCaseAscent; }
Returns the LowerCaseDescent parameter for the font (height of lowercase p).
Returns:The LowerCaseDescent parameter.
/** * Returns the LowerCaseDescent parameter for the font (height of lowercase p). * * @return The LowerCaseDescent parameter. */
public int getLowerCaseDescent() { return etmLowerCaseDescent; }
Tells whether the font has proportional character spacing.
Returns:ex. true for Times, false for Courier.
/** * Tells whether the font has proportional character spacing. * * @return ex. true for Times, false for Courier. */
public boolean getIsProportional() { return ((dfPitchAndFamily & 1) == 1); }
Returns the bounding box for the font. Note: this value is just an approximation, it does not really exist in the PFM file.
Returns:The calculated Font BBox.
/** * Returns the bounding box for the font. * Note: this value is just an approximation, * it does not really exist in the PFM file. * * @return The calculated Font BBox. */
public int[] getFontBBox() { int[] bbox = new int[4]; // Just guessing.... if (!getIsProportional() && (dfAvgWidth == dfMaxWidth)) { bbox[0] = -20; } else { bbox[0] = -100; } bbox[1] = getLowerCaseDescent() - 5; bbox[2] = dfMaxWidth + 10; bbox[3] = getLowerCaseAscent() + 5; return bbox; }
Indicates whether the font is non-symbolic (Font uses the Adobe standard Latin character set or a subset of it).
Returns:true if the font is non-symbolic
/** * Indicates whether the font is non-symbolic (Font uses the Adobe standard Latin character * set or a subset of it). * @return true if the font is non-symbolic */
public boolean isNonSymbolic() { return (dfCharSet != 2); //!= Symbol fonts }
Returns the characteristics flags for the font as needed for a PDF font descriptor (See PDF specs).
Returns:The characteristics flags.
/** * Returns the characteristics flags for the font as * needed for a PDF font descriptor (See PDF specs). * * @return The characteristics flags. */
public int getFlags() { int flags = 0; if (!getIsProportional()) { flags |= 1; //bit 1: FixedPitch } if (isNonSymbolic()) { flags |= 32; //bit 6: Nonsymbolic } else { flags |= 4; //bit 3: Symbolic } //int serif = dfPitchAndFamily & 0xFFFE; if ((dfPitchAndFamily & 16) != 0) { flags |= 2; //bit 2: Serif } if ((dfPitchAndFamily & 64) != 0) { flags |= 8; //bit 4: Script } if (dfItalic != 0) { flags |= 64; //bit 7: Italic } return flags; }
Returns the width of the dominant vertical stems of the font. Note: this value is just an approximation, it does not really exist in the PFM file.
Returns:The vertical stem width.
/** * Returns the width of the dominant vertical stems of the font. * Note: this value is just an approximation, * it does not really exist in the PFM file. * * @return The vertical stem width. */
public int getStemV() { // Just guessing.... if (dfItalic != 0) { return (int) Math.round(dfMinWidth * 0.25); } else { return (int) Math.round(dfMinWidth * 0.6); } }
Returns the italic angle of the font. Note: this value is just an approximation, it does not really exist in the PFM file.
Returns:The italic angle.
/** * Returns the italic angle of the font. * Note: this value is just an approximation, * it does not really exist in the PFM file. * * @return The italic angle. */
public int getItalicAngle() { if (dfItalic != 0) { return -16; // Just guessing.... } else { return 0; } }
Returns the width of a character
Params:
  • which – The number of the character for which the width is requested.
Returns:The width of a character.
/** * Returns the width of a character * * @param which The number of the character for which the width is requested. * @return The width of a character. */
public int getCharWidth(short which) { if (extentTable != null) { return extentTable[which - dfFirstChar]; } else { //Fixed-width font (PFM may have no extent table) //we'll just use the average width return this.dfAvgWidth; } } }