123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493 |
- /*
- * 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$ */
-
- package org.apache.fop.fonts.type1;
-
- // Java
- import java.io.ByteArrayInputStream;
- import java.io.IOException;
- import java.io.InputStream;
- 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.
- */
- 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 kerningTab = new java.util.HashMap();
-
- /**
- * logging instance
- */
- protected Log log = LogFactory.getLog(PFMFile.class);
-
- /**
- * 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!
- 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];
- bufin.read(b);
- if (new String(b, "US-ASCII").equalsIgnoreCase("StartFontMetrics")) {
- //Found the header of a AFM file!
- 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.
- *
- * @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 {
- inStream.skip(80);
- dfItalic = inStream.readByte();
- inStream.skip(2);
- dfWeight = inStream.readShort();
- dfCharSet = inStream.readByte();
- inStream.skip(4);
- dfPitchAndFamily = inStream.readByte();
- dfAvgWidth = inStream.readShort();
- dfMaxWidth = inStream.readShort();
- dfFirstChar = inStream.readByte();
- dfLastChar = inStream.readByte();
- inStream.skip(8);
- long faceOffset = inStream.readInt();
-
- inStream.reset();
- inStream.skip(faceOffset);
- windowsName = inStream.readString();
-
- inStream.reset();
- inStream.skip(117);
- }
-
- /**
- * 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();
- inStream.skip(4); //Skip dfOriginTable
- final long kernPairOffset = inStream.readInt();
- inStream.skip(4); //Skip dfTrackKernTable
- long driverInfoOffset = inStream.readInt();
-
- if (kernPairOffset > 0) {
- inStream.reset();
- inStream.skip(kernPairOffset);
- loadKernPairs(inStream);
- }
-
- inStream.reset();
- inStream.skip(driverInfoOffset);
- postScriptName = inStream.readString();
-
- if (extMetricsOffset != 0) {
- inStream.reset();
- inStream.skip(extMetricsOffset);
- loadExtMetrics(inStream);
- }
- if (extentTableOffset != 0) {
- inStream.reset();
- inStream.skip(extentTableOffset);
- loadExtentTable(inStream);
- }
-
- }
-
- /**
- * 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 adjTab = (Map)kerningTab.get(new Integer(g1));
- if (adjTab == null) {
- adjTab = new java.util.HashMap();
- }
- adjTab.put(new Integer(g2), new Integer(adj));
- kerningTab.put(new Integer(g1), adjTab);
- }
- }
-
- /**
- * 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.");
- }
- inStream.skip(12); //Skip etmPointSize, etmOrientation, etmMasterHeight,
- //etmMinScale, etmMaxScale, emtMasterUnits
- 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.
- *
- * @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.
- *
- * @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
- *
- * @return A Map containing the kerning table
- */
- public Map getKerning() {
- return kerningTab;
- }
-
- /**
- * Returns the Postscript name of the font.
- *
- * @return The Postscript name.
- */
- public String getPostScriptName() {
- return postScriptName;
- }
-
- /**
- * 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.
- *
- * @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.
- *
- * @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.
- *
- * @return The number of the last character.
- */
- public short getLastChar() {
- return dfLastChar;
- }
-
- /**
- * 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).
- *
- * @return The CapHeight parameter.
- */
- public int getXHeight() {
- return etmXHeight;
- }
-
- /**
- * 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).
- *
- * @return The LowerCaseDescent parameter.
- */
- public int getLowerCaseDescent() {
- return etmLowerCaseDescent;
- }
-
- /**
- * 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.
- *
- * @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).
- * @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).
- *
- * @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.
- *
- * @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.
- *
- * @return The italic angle.
- */
- public int getItalicAngle() {
- if (dfItalic != 0) {
- return -16; // Just guessing....
- } else {
- return 0;
- }
- }
-
- /**
- * 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;
- }
- }
-
- }
|