]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
Brought the branch in sync with rev. 1562429 of trunk
authorVincent Hennebert <vhennebert@apache.org>
Thu, 30 Jan 2014 15:51:03 +0000 (15:51 +0000)
committerVincent Hennebert <vhennebert@apache.org>
Thu, 30 Jan 2014 15:51:03 +0000 (15:51 +0000)
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/Temp_FopFontsForSVG@1562866 13f79535-47bb-0310-9956-ffa450edef68

1  2 
build.xml
src/java/org/apache/fop/fonts/MultiByteFont.java
src/java/org/apache/fop/fonts/SingleByteFont.java
src/java/org/apache/fop/fonts/truetype/OFFontLoader.java
src/java/org/apache/fop/fonts/truetype/OpenFont.java
src/java/org/apache/fop/fonts/type1/Type1FontLoader.java
src/java/org/apache/fop/render/ps/PSImageHandlerSVG.java
src/java/org/apache/fop/svg/SimpleSVGUserAgent.java
test/java/org/apache/fop/fonts/truetype/TTFFileTestCase.java
test/java/org/apache/fop/svg/font/FontInfoBuilder.java

diff --cc build.xml
index e1770ec559bffb708495602917c30ef12e91d70f,b730584f1d8aa2da1875a8ddac64a9c154ee6534..ba9f2e09b2b7073f20fbbcc5a9b8abf61139f85a
+++ b/build.xml
@@@ -563,16 -563,14 +563,17 @@@ list of possible build targets
        <include name="org/apache/fop/apps/Fop.class"/>
        <include name="org/apache/fop/apps/FOPException.class"/>
        <include name="org/apache/fop/apps/io/**"/>
++      <include name="org/apache/fop/area/AreaTreeControl*"/>
        <include name="org/apache/fop/complexscripts/fonts/*.class"/>
        <include name="org/apache/fop/complexscripts/util/GlyphTester.class"/>
 +      <include name="org/apache/fop/events/EventProducer.class"/>
        <include name="org/apache/fop/fo/Constants.class"/>
        <include name="org/apache/fop/fo/FOTreeBuilder.class"/>
--      <include name="org/apache/fop/area/AreaTreeControl*"/>
++      <include name="org/apache/fop/image/loader/batik/BatikImageFlavors*.class"/>
        <include name="org/apache/fop/svg/**"/>
        <include name="org/apache/fop/fonts/**"/>
 -      <include name="org/apache/fop/image/loader/batik/BatikImageFlavors*.class"/>
++      <include name="org/apache/fop/render/shading/**"/>
 +      <include name="org/apache/fop/traits/MinOptMax.class"/>
-       <include name="org/apache/fop/image/loader/batik/BatikImageFlavors*.class"/>
        <include name="org/apache/fop/util/CMYKColorSpace*.class"/>
        <include name="org/apache/fop/util/Color*.class"/>
        <include name="org/apache/fop/util/ASCII*.class"/>
index 1d1186dcb9d9d6cbae8a861757210b61d6c3e382,68be7bed807d8ff3c0cef6580c0b77e2aab00757..cc3d0665099cb37a78dacf04d4eda4f9fefd0dd9
@@@ -68,9 -68,15 +69,18 @@@ public class MultiByteFont extends CIDF
      private int firstUnmapped;
      private int lastUnmapped;
  
 +    /** Contains the character bounding boxes for all characters in the font */
 +    protected Rectangle[] boundingBoxes;
 +
+     private boolean isOTFFile = false;
+     // since for most users the most likely glyphs are in the first cmap segments we store their mapping.
+     private static final int NUM_MOST_LIKELY_GLYPHS = 256;
+     private int[] mostLikelyGlyphs = new int[NUM_MOST_LIKELY_GLYPHS];
+     //A map to store each used glyph from the CID set against the glyph name.
+     private LinkedHashMap<Integer, String> usedGlyphNames = new LinkedHashMap<Integer, String>();
      /**
       * Default constructor
       */
index 0000000000000000000000000000000000000000,f15837bb8bdf156628c152987de7fa880fbf5c75..7168389ff335a5e8d38239e48aec5feea4ecab97
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,261 +1,270 @@@
 -            int[] wx = otf.getWidths();
 -            multiFont.setWidthArray(wx);
+ /*
+  * 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.truetype;
++import java.awt.Rectangle;
+ import java.io.IOException;
+ import java.io.InputStream;
+ import java.net.URI;
+ import java.util.Map;
+ import java.util.Set;
+ import org.apache.commons.io.IOUtils;
+ import org.apache.fop.apps.io.InternalResourceResolver;
+ import org.apache.fop.fonts.CIDFontType;
+ import org.apache.fop.fonts.CMapSegment;
+ import org.apache.fop.fonts.EmbeddingMode;
+ import org.apache.fop.fonts.EncodingMode;
+ import org.apache.fop.fonts.FontLoader;
+ import org.apache.fop.fonts.FontType;
+ import org.apache.fop.fonts.MultiByteFont;
+ import org.apache.fop.fonts.NamedCharacter;
+ import org.apache.fop.fonts.SingleByteFont;
+ import org.apache.fop.fonts.truetype.OpenFont.PostScriptVersion;
+ import org.apache.fop.util.HexEncoder;
+ /**
+  * Loads a TrueType font into memory directly from the original font file.
+  */
+ public class OFFontLoader extends FontLoader {
+     private MultiByteFont multiFont;
+     private SingleByteFont singleFont;
+     private final String subFontName;
+     private EncodingMode encodingMode;
+     private EmbeddingMode embeddingMode;
+     /**
+      * Default constructor
+      * @param fontFileURI the URI representing the font file
+      * @param resourceResolver the resource resolver for font URI resolution
+      */
+     public OFFontLoader(URI fontFileURI, InternalResourceResolver resourceResolver) {
+         this(fontFileURI, null, true, EmbeddingMode.AUTO, EncodingMode.AUTO, true, true, resourceResolver);
+     }
+     /**
+      * Additional constructor for TrueType Collections.
+      * @param fontFileURI the URI representing the font file
+      * @param subFontName the sub-fontname of a font in a TrueType Collection (or null for normal
+      *          TrueType fonts)
+      * @param embedded indicates whether the font is embedded or referenced
+      * @param embeddingMode the embedding mode of the font
+      * @param encodingMode the requested encoding mode
+      * @param useKerning true to enable loading kerning info if available, false to disable
+      * @param useAdvanced true to enable loading advanced info if available, false to disable
+      * @param resolver the FontResolver for font URI resolution
+      */
+     public OFFontLoader(URI fontFileURI, String subFontName, boolean embedded,
+             EmbeddingMode embeddingMode, EncodingMode encodingMode, boolean useKerning,
+             boolean useAdvanced, InternalResourceResolver resolver) {
+         super(fontFileURI, embedded, useKerning, useAdvanced, resolver);
+         this.subFontName = subFontName;
+         this.encodingMode = encodingMode;
+         this.embeddingMode = embeddingMode;
+         if (this.encodingMode == EncodingMode.AUTO) {
+             this.encodingMode = EncodingMode.CID; //Default to CID mode for TrueType
+         }
+         if (this.embeddingMode == EmbeddingMode.AUTO) {
+             this.embeddingMode = EmbeddingMode.SUBSET;
+         }
+     }
+     /** {@inheritDoc} */
+     protected void read() throws IOException {
+         read(this.subFontName);
+     }
+     /**
+      * Reads a TrueType font.
+      * @param ttcFontName the TrueType sub-font name of TrueType Collection (may be null for
+      *    normal TrueType fonts)
+      * @throws IOException if an I/O error occurs
+      */
+     private void read(String ttcFontName) throws IOException {
+         InputStream in = resourceResolver.getResource(this.fontFileURI);
+         try {
+             FontFileReader reader = new FontFileReader(in);
+             String header = readHeader(reader);
+             boolean isCFF = header.equals("OTTO");
+             OpenFont otf = (isCFF) ? new OTFFile() : new TTFFile(useKerning, useAdvanced);
+             boolean supported = otf.readFont(reader, header, ttcFontName);
+             if (!supported) {
+                 throw new IOException("The font does not have a Unicode cmap table: " + fontFileURI);
+             }
+             buildFont(otf, ttcFontName);
+             loaded = true;
+         } finally {
+             IOUtils.closeQuietly(in);
+         }
+     }
+     public static String readHeader(FontFileReader fontFile) throws IOException {
+         if (fontFile != null) {
+             fontFile.seekSet(0);
+             return fontFile.readTTFString(4); // TTF_FIXED_SIZE (4 bytes)
+         }
+         return null;
+     }
+     private void buildFont(OpenFont otf, String ttcFontName) {
+         boolean isCid = this.embedded;
+         if (this.encodingMode == EncodingMode.SINGLE_BYTE) {
+             isCid = false;
+         }
+         if (isCid) {
+             multiFont = new MultiByteFont(resourceResolver, embeddingMode);
+             multiFont.setIsOTFFile(otf instanceof OTFFile);
+             returnFont = multiFont;
+             multiFont.setTTCName(ttcFontName);
+         } else {
+             singleFont = new SingleByteFont(resourceResolver);
+             returnFont = singleFont;
+         }
+         returnFont.setFontName(otf.getPostScriptName());
+         returnFont.setFullName(otf.getFullName());
+         returnFont.setFamilyNames(otf.getFamilyNames());
+         returnFont.setFontSubFamilyName(otf.getSubFamilyName());
+         returnFont.setCapHeight(otf.getCapHeight());
+         returnFont.setXHeight(otf.getXHeight());
+         returnFont.setAscender(otf.getLowerCaseAscent());
+         returnFont.setDescender(otf.getLowerCaseDescent());
+         returnFont.setFontBBox(otf.getFontBBox());
++        returnFont.setUnderlinePosition(otf.getUnderlinePosition() - otf.getUnderlineThickness() / 2);
++        returnFont.setUnderlineThickness(otf.getUnderlineThickness());
++        returnFont.setStrikeoutPosition(otf.getStrikeoutPosition() - otf.getStrikeoutThickness() / 2);
++        returnFont.setStrikeoutThickness(otf.getStrikeoutThickness());
+         returnFont.setFlags(otf.getFlags());
+         returnFont.setStemV(Integer.parseInt(otf.getStemV())); //not used for TTF
+         returnFont.setItalicAngle(Integer.parseInt(otf.getItalicAngle()));
+         returnFont.setMissingWidth(0);
+         returnFont.setWeight(otf.getWeightClass());
+         returnFont.setEmbeddingMode(this.embeddingMode);
+         if (isCid) {
+             if (otf instanceof OTFFile) {
+                 multiFont.setCIDType(CIDFontType.CIDTYPE0);
+             } else {
+                 multiFont.setCIDType(CIDFontType.CIDTYPE2);
+             }
 -            copyWidthsSingleByte(otf);
++            multiFont.setWidthArray(otf.getWidths());
++            multiFont.setBBoxArray(otf.getBoundingBoxes());
+         } else {
+             singleFont.setFontType(FontType.TRUETYPE);
+             singleFont.setEncoding(otf.getCharSetName());
+             returnFont.setFirstChar(otf.getFirstChar());
+             returnFont.setLastChar(otf.getLastChar());
+             singleFont.setTrueTypePostScriptVersion(otf.getPostScriptVersion());
 -    private void copyWidthsSingleByte(OpenFont otf) {
++            copyGlyphMetricsSingleByte(otf);
+         }
+         returnFont.setCMap(getCMap(otf));
+         if (otf.getKerning() != null && useKerning) {
+             copyKerning(otf, isCid);
+         }
+         if (useAdvanced) {
+             copyAdvanced(otf);
+         }
+         if (this.embedded) {
+             if (otf.isEmbeddable()) {
+                 returnFont.setEmbedURI(this.fontFileURI);
+             } else {
+                 String msg = "The font " + this.fontFileURI + " is not embeddable due to a"
+                         + " licensing restriction.";
+                 throw new RuntimeException(msg);
+             }
+         }
+     }
+     private CMapSegment[] getCMap(OpenFont otf) {
+         CMapSegment[] array = new CMapSegment[otf.getCMaps().size()];
+         return otf.getCMaps().toArray(array);
+     }
 -                            singleFont.addUnencodedCharacter(nc, wx[glyphIndex]);
++    private void copyGlyphMetricsSingleByte(OpenFont otf) {
+         int[] wx = otf.getWidths();
++        Rectangle[] bboxes = otf.getBoundingBoxes();
+         for (int i = singleFont.getFirstChar(); i <= singleFont.getLastChar(); i++) {
+             singleFont.setWidth(i, otf.getCharWidth(i));
++            int[] bbox = otf.getBBox(i);
++            singleFont.setBoundingBox(i,
++                    new Rectangle(bbox[0], bbox[1], bbox[2] - bbox[0], bbox[3] - bbox[1]));
+         }
+         for (CMapSegment segment : otf.getCMaps()) {
+             if (segment.getUnicodeStart() < 0xFFFE) {
+                 for (char u = (char)segment.getUnicodeStart(); u <= segment.getUnicodeEnd(); u++) {
+                     int codePoint = singleFont.getEncoding().mapChar(u);
+                     if (codePoint <= 0) {
+                         int glyphIndex = segment.getGlyphStartIndex() + u - segment.getUnicodeStart();
+                         String glyphName = otf.getGlyphName(glyphIndex);
+                         if (glyphName.length() == 0 && otf.getPostScriptVersion() != PostScriptVersion.V2) {
+                             glyphName = "u" + HexEncoder.encode(u);
+                         }
+                         if (glyphName.length() > 0) {
+                             String unicode = Character.toString(u);
+                             NamedCharacter nc = new NamedCharacter(glyphName, unicode);
++                            singleFont.addUnencodedCharacter(nc, wx[glyphIndex], bboxes[glyphIndex]);
+                         }
+                     }
+                 }
+             }
+         }
+     }
+     /**
+      * Copy kerning information.
+      */
+     private void copyKerning(OpenFont otf, boolean isCid) {
+         // Get kerning
+         Set<Integer> kerningSet;
+         if (isCid) {
+             kerningSet = otf.getKerning().keySet();
+         } else {
+             kerningSet = otf.getAnsiKerning().keySet();
+         }
+         for (Integer kpx1 : kerningSet) {
+             Map<Integer, Integer> h2;
+             if (isCid) {
+                 h2 = otf.getKerning().get(kpx1);
+             } else {
+                 h2 = otf.getAnsiKerning().get(kpx1);
+             }
+             returnFont.putKerningEntry(kpx1, h2);
+         }
+     }
+     /**
+      * Copy advanced typographic information.
+      */
+     private void copyAdvanced(OpenFont otf) {
+         if (returnFont instanceof MultiByteFont) {
+             MultiByteFont mbf = (MultiByteFont) returnFont;
+             mbf.setGDEF(otf.getGDEF());
+             mbf.setGSUB(otf.getGSUB());
+             mbf.setGPOS(otf.getGPOS());
+         }
+     }
+ }
index 0000000000000000000000000000000000000000,ce9a2b3887547931e7d9b5537cdaffd7a91dc94b..3f4765cdc145eb1d6ba20fe748179dd7db17be61
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,1937 +1,1971 @@@
 -
+ /*
+  * 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.truetype;
++import java.awt.Rectangle;
+ import java.io.FileInputStream;
+ import java.io.IOException;
+ import java.io.InputStream;
+ import java.util.ArrayList;
+ import java.util.Arrays;
+ import java.util.BitSet;
+ import java.util.Collections;
+ import java.util.Comparator;
+ import java.util.HashMap;
+ import java.util.HashSet;
+ import java.util.Iterator;
+ import java.util.List;
+ import java.util.Map;
+ import java.util.Map.Entry;
+ import java.util.Set;
+ import java.util.SortedSet;
+ import java.util.TreeSet;
+ import org.apache.commons.io.IOUtils;
+ import org.apache.commons.logging.Log;
+ import org.apache.commons.logging.LogFactory;
+ import org.apache.xmlgraphics.fonts.Glyphs;
+ import org.apache.fop.complexscripts.fonts.AdvancedTypographicTableFormatException;
+ import org.apache.fop.complexscripts.fonts.GlyphDefinitionTable;
+ import org.apache.fop.complexscripts.fonts.GlyphPositioningTable;
+ import org.apache.fop.complexscripts.fonts.GlyphSubstitutionTable;
+ import org.apache.fop.complexscripts.fonts.OTFAdvancedTypographicTableReader;
+ import org.apache.fop.fonts.CMapSegment;
+ import org.apache.fop.fonts.FontUtil;
+ import org.apache.fop.fonts.MultiByteFont;
+ public abstract class OpenFont {
+     static final byte NTABS = 24;
+     static final int MAX_CHAR_CODE = 255;
+     static final int ENC_BUF_SIZE = 1024;
+     private static final String[] MAC_GLYPH_ORDERING = {
+         /* 0x000 */
+         ".notdef",          ".null",         "nonmarkingreturn", "space",
+         "exclam",           "quotedbl",      "numbersign",       "dollar",
+         "percent",          "ampersand",     "quotesingle",      "parenleft",
+         "parenright",       "asterisk",      "plus",             "comma",
+         /* 0x010 */
+         "hyphen",           "period",        "slash",            "zero",
+         "one",              "two",           "three",            "four",
+         "five",             "six",           "seven",            "eight",
+         "nine",             "colon",         "semicolon",        "less",
+         /* 0x020 */
+         "equal",            "greater",       "question",         "at",
+         "A",                "B",             "C",                "D",
+         "E",                "F",             "G",                "H",
+         "I",                "J",             "K",                "L",
+         /* 0x030 */
+         "M",                "N",             "O",                "P",
+         "Q",                "R",             "S",                "T",
+         "U",                "V",             "W",                "X",
+         "Y",                "Z",             "bracketleft",      "backslash",
+         /* 0x040 */
+         "bracketright",     "asciicircum",   "underscore",       "grave",
+         "a",                "b",             "c",                "d",
+         "e",                "f",             "g",                "h",
+         "i",                "j",             "k",                "l",
+         /* 0x050 */
+         "m",                "n",             "o",                "p",
+         "q",                "r",             "s",                "t",
+         "u",                "v",             "w",                "x",
+         "y",                "z",             "braceleft",        "bar",
+         /* 0x060 */
+         "braceright",       "asciitilde",    "Adieresis",        "Aring",
+         "Ccedilla",         "Eacute",        "Ntilde",           "Odieresis",
+         "Udieresis",        "aacute",        "agrave",           "acircumflex",
+         "adieresis",        "atilde",        "aring",            "ccedilla",
+         /* 0x070 */
+         "eacute",           "egrave",        "ecircumflex",      "edieresis",
+         "iacute",           "igrave",        "icircumflex",      "idieresis",
+         "ntilde",           "oacute",        "ograve",           "ocircumflex",
+         "odieresis",        "otilde",        "uacute",           "ugrave",
+         /* 0x080 */
+         "ucircumflex",      "udieresis",     "dagger",           "degree",
+         "cent",             "sterling",      "section",          "bullet",
+         "paragraph",        "germandbls",    "registered",       "copyright",
+         "trademark",        "acute",         "dieresis",         "notequal",
+         /* 0x090 */
+         "AE",               "Oslash",        "infinity",         "plusminus",
+         "lessequal",        "greaterequal",  "yen",              "mu",
+         "partialdiff",      "summation",     "product",          "pi",
+         "integral",         "ordfeminine",   "ordmasculine",     "Omega",
+         /* 0x0A0 */
+         "ae",               "oslash",        "questiondown",     "exclamdown",
+         "logicalnot",       "radical",       "florin",           "approxequal",
+         "Delta",            "guillemotleft", "guillemotright",   "ellipsis",
+         "nonbreakingspace", "Agrave",        "Atilde",           "Otilde",
+         /* 0x0B0 */
+         "OE",               "oe",            "endash",           "emdash",
+         "quotedblleft",     "quotedblright", "quoteleft",        "quoteright",
+         "divide",           "lozenge",       "ydieresis",        "Ydieresis",
+         "fraction",         "currency",      "guilsinglleft",    "guilsinglright",
+         /* 0x0C0 */
+         "fi",               "fl",            "daggerdbl",        "periodcentered",
+         "quotesinglbase",   "quotedblbase",  "perthousand",      "Acircumflex",
+         "Ecircumflex",      "Aacute",        "Edieresis",        "Egrave",
+         "Iacute",           "Icircumflex",   "Idieresis",        "Igrave",
+         /* 0x0D0 */
+         "Oacute",           "Ocircumflex",   "apple",            "Ograve",
+         "Uacute",           "Ucircumflex",   "Ugrave",           "dotlessi",
+         "circumflex",       "tilde",         "macron",           "breve",
+         "dotaccent",        "ring",          "cedilla",          "hungarumlaut",
+         /* 0x0E0 */
+         "ogonek",           "caron",         "Lslash",           "lslash",
+         "Scaron",           "scaron",        "Zcaron",           "zcaron",
+         "brokenbar",        "Eth",           "eth",              "Yacute",
+         "yacute",           "Thorn",         "thorn",            "minus",
+         /* 0x0F0 */
+         "multiply",         "onesuperior",   "twosuperior",      "threesuperior",
+         "onehalf",          "onequarter",    "threequarters",    "franc",
+         "Gbreve",           "gbreve",        "Idotaccent",       "Scedilla",
+         "scedilla",         "Cacute",        "cacute",           "Ccaron",
+         /* 0x100 */
+         "ccaron",           "dcroat"
+     };
+     /** The FontFileReader used to read this TrueType font. */
+     protected FontFileReader fontFile;
+     /** Set to true to get even more debug output than with level DEBUG */
+     public static final boolean TRACE_ENABLED = false;
+     private static final String ENCODING = "WinAnsiEncoding";    // Default encoding
+     private static final short FIRST_CHAR = 0;
+     protected boolean useKerning = false;
+     private boolean isEmbeddable = true;
+     private boolean hasSerifs = true;
+     /**
+      * Table directory
+      */
+     protected Map<OFTableName, OFDirTabEntry> dirTabs;
+     private Map<Integer, Map<Integer, Integer>> kerningTab; // for CIDs
+     private Map<Integer, Map<Integer, Integer>> ansiKerningTab; // For winAnsiEncoding
+     private List<CMapSegment> cmaps;
+     protected List<UnicodeMapping> unicodeMappings;
+     private int upem;                                // unitsPerEm from "head" table
+     private int nhmtx;                               // Number of horizontal metrics
+     private PostScriptVersion postScriptVersion;
+     protected int locaFormat;
+     /**
+      * Offset to last loca
+      */
+     protected long lastLoca = 0;
+     protected int numberOfGlyphs; // Number of glyphs in font (read from "maxp" table)
+     /**
+      * Contains glyph data
+      */
+     protected OFMtxEntry[] mtxTab;                  // Contains glyph data
+     protected String postScriptName = "";
+     protected String fullName = "";
+     protected String notice = "";
+     protected final Set<String> familyNames = new HashSet<String>();
+     protected String subFamilyName = "";
+     private long italicAngle = 0;
+     private long isFixedPitch = 0;
+     private int fontBBox1 = 0;
+     private int fontBBox2 = 0;
+     private int fontBBox3 = 0;
+     private int fontBBox4 = 0;
+     private int capHeight = 0;
+     private int os2CapHeight = 0;
++    private int underlinePosition;
++    private int underlineThickness;
++    private int strikeoutPosition;
++    private int strikeoutThickness;
+     private int xHeight = 0;
+     private int os2xHeight = 0;
+     //Effective ascender/descender
+     private int ascender = 0;
+     private int descender = 0;
+     //Ascender/descender from hhea table
+     private int hheaAscender = 0;
+     private int hheaDescender = 0;
+     //Ascender/descender from OS/2 table
+     private int os2Ascender = 0;
+     private int os2Descender = 0;
+     private int usWeightClass = 0;
+     private short lastChar = 0;
+     private int[] ansiWidth;
+     private Map<Integer, List<Integer>> ansiIndex;
+     // internal mapping of glyph indexes to unicode indexes
+     // used for quick mappings in this class
+     private final Map<Integer, Integer> glyphToUnicodeMap = new HashMap<Integer, Integer>();
+     private final Map<Integer, Integer> unicodeToGlyphMap = new HashMap<Integer, Integer>();
+     private boolean isCFF;
+     // advanced typographic table support
+     protected boolean useAdvanced = false;
+     protected OTFAdvancedTypographicTableReader advancedTableReader;
+     /**
+      * Version of the PostScript table (<q>post</q>) contained in this font.
+      */
+     public static enum PostScriptVersion {
+         /** PostScript table version 1.0. */
+         V1,
+         /** PostScript table version 2.0. */
+         V2,
+         /** PostScript table version 3.0. */
+         V3,
+         /** Unknown version of the PostScript table. */
+         UNKNOWN;
+     }
+     /**
+      * logging instance
+      */
+     protected Log log = LogFactory.getLog(TTFFile.class);
+     public OpenFont() {
+         this(true, false);
+     }
+     /**
+      * Constructor
+      * @param useKerning true if kerning data should be loaded
+      * @param useAdvanced true if advanced typographic tables should be loaded
+      */
+     public OpenFont(boolean useKerning, boolean useAdvanced) {
+         this.useKerning = useKerning;
+         this.useAdvanced = useAdvanced;
+     }
+     /**
+      * Key-value helper class.
+      */
+     final class UnicodeMapping implements Comparable {
+         private final int unicodeIndex;
+         private final int glyphIndex;
+         UnicodeMapping(int glyphIndex, int unicodeIndex) {
+             this.unicodeIndex = unicodeIndex;
+             this.glyphIndex = glyphIndex;
+             glyphToUnicodeMap.put(new Integer(glyphIndex), new Integer(unicodeIndex));
+             unicodeToGlyphMap.put(new Integer(unicodeIndex), new Integer(glyphIndex));
+         }
+         /**
+          * Returns the glyphIndex.
+          * @return the glyph index
+          */
+         public int getGlyphIndex() {
+             return glyphIndex;
+         }
+         /**
+          * Returns the unicodeIndex.
+          * @return the Unicode index
+          */
+         public int getUnicodeIndex() {
+             return unicodeIndex;
+         }
+         /** {@inheritDoc} */
+         public int hashCode() {
+             int hc = unicodeIndex;
+             hc = 19 * hc + (hc ^ glyphIndex);
+             return hc;
+         }
+         /** {@inheritDoc} */
+         public boolean equals(Object o) {
+             if (o instanceof UnicodeMapping) {
+                 UnicodeMapping m = (UnicodeMapping) o;
+                 if (unicodeIndex != m.unicodeIndex) {
+                     return false;
+                 } else {
+                     return (glyphIndex == m.glyphIndex);
+                 }
+             } else {
+                 return false;
+             }
+         }
+         /** {@inheritDoc} */
+         public int compareTo(Object o) {
+             if (o instanceof UnicodeMapping) {
+                 UnicodeMapping m = (UnicodeMapping) o;
+                 if (unicodeIndex > m.unicodeIndex) {
+                     return 1;
+                 } else if (unicodeIndex < m.unicodeIndex) {
+                     return -1;
+                 } else {
+                     return 0;
+                 }
+             } else {
+                 return -1;
+             }
+         }
+     }
+     /**
+      * Obtain directory table entry.
+      * @param name (tag) of entry
+      * @return a directory table entry or null if none found
+      */
+     public OFDirTabEntry getDirectoryEntry(OFTableName name) {
+         return dirTabs.get(name);
+     }
+     /**
+      * Position inputstream to position indicated
+      * in the dirtab offset + offset
+      * @param in font file reader
+      * @param tableName (tag) of table
+      * @param offset from start of table
+      * @return true if seek succeeded
+      * @throws IOException if I/O exception occurs during seek
+      */
+     public boolean seekTab(FontFileReader in, OFTableName tableName,
+                   long offset) throws IOException {
+         OFDirTabEntry dt = dirTabs.get(tableName);
+         if (dt == null) {
+             log.error("Dirtab " + tableName.getName() + " not found.");
+             return false;
+         } else {
+             in.seekSet(dt.getOffset() + offset);
+         }
+         return true;
+     }
+     /**
+      * Convert from truetype unit to pdf unit based on the
+      * unitsPerEm field in the "head" table
+      * @param n truetype unit
+      * @return pdf unit
+      */
+     public int convertTTFUnit2PDFUnit(int n) {
+         int ret;
+         if (n < 0) {
+             long rest1 = n % upem;
+             long storrest = 1000 * rest1;
+             long ledd2 = (storrest != 0 ? rest1 / storrest : 0);
+             ret = -((-1000 * n) / upem - (int)ledd2);
+         } else {
+             ret = (n / upem) * 1000 + ((n % upem) * 1000) / upem;
+         }
+         return ret;
+     }
+     /**
+      * Read the cmap table,
+      * return false if the table is not present or only unsupported
+      * tables are present. Currently only unicode cmaps are supported.
+      * Set the unicodeIndex in the TTFMtxEntries and fills in the
+      * cmaps vector.
+      */
+     protected boolean readCMAP() throws IOException {
+         unicodeMappings = new ArrayList<OpenFont.UnicodeMapping>();
+         seekTab(fontFile, OFTableName.CMAP, 2);
+         int numCMap = fontFile.readTTFUShort();    // Number of cmap subtables
+         long cmapUniOffset = 0;
+         long symbolMapOffset = 0;
+         if (log.isDebugEnabled()) {
+             log.debug(numCMap + " cmap tables");
+         }
+         //Read offset for all tables. We are only interested in the unicode table
+         for (int i = 0; i < numCMap; i++) {
+             int cmapPID = fontFile.readTTFUShort();
+             int cmapEID = fontFile.readTTFUShort();
+             long cmapOffset = fontFile.readTTFLong();
+             if (log.isDebugEnabled()) {
+                 log.debug("Platform ID: " + cmapPID + " Encoding: " + cmapEID);
+             }
+             if (cmapPID == 3 && cmapEID == 1) {
+                 cmapUniOffset = cmapOffset;
+             }
+             if (cmapPID == 3 && cmapEID == 0) {
+                 symbolMapOffset = cmapOffset;
+             }
+         }
+         if (cmapUniOffset > 0) {
+             return readUnicodeCmap(cmapUniOffset, 1);
+         } else if (symbolMapOffset > 0) {
+             return readUnicodeCmap(symbolMapOffset, 0);
+         } else {
+             log.fatal("Unsupported TrueType font: No Unicode or Symbol cmap table"
+                     + " not present. Aborting");
+             return false;
+         }
+     }
+     private boolean readUnicodeCmap(long cmapUniOffset, int encodingID)
+             throws IOException {
+         //Read CMAP table and correct mtxTab.index
+         int mtxPtr = 0;
+         // Read unicode cmap
+         seekTab(fontFile, OFTableName.CMAP, cmapUniOffset);
+         int cmapFormat = fontFile.readTTFUShort();
+         /*int cmap_length =*/ fontFile.readTTFUShort(); //skip cmap length
+         if (log.isDebugEnabled()) {
+             log.debug("CMAP format: " + cmapFormat);
+         }
+         if (cmapFormat == 4) {
+             fontFile.skip(2);    // Skip version number
+             int cmapSegCountX2 = fontFile.readTTFUShort();
+             int cmapSearchRange = fontFile.readTTFUShort();
+             int cmapEntrySelector = fontFile.readTTFUShort();
+             int cmapRangeShift = fontFile.readTTFUShort();
+             if (log.isDebugEnabled()) {
+                 log.debug("segCountX2   : " + cmapSegCountX2);
+                 log.debug("searchRange  : " + cmapSearchRange);
+                 log.debug("entrySelector: " + cmapEntrySelector);
+                 log.debug("rangeShift   : " + cmapRangeShift);
+             }
+             int[] cmapEndCounts = new int[cmapSegCountX2 / 2];
+             int[] cmapStartCounts = new int[cmapSegCountX2 / 2];
+             int[] cmapDeltas = new int[cmapSegCountX2 / 2];
+             int[] cmapRangeOffsets = new int[cmapSegCountX2 / 2];
+             for (int i = 0; i < (cmapSegCountX2 / 2); i++) {
+                 cmapEndCounts[i] = fontFile.readTTFUShort();
+             }
+             fontFile.skip(2);    // Skip reservedPad
+             for (int i = 0; i < (cmapSegCountX2 / 2); i++) {
+                 cmapStartCounts[i] = fontFile.readTTFUShort();
+             }
+             for (int i = 0; i < (cmapSegCountX2 / 2); i++) {
+                 cmapDeltas[i] = fontFile.readTTFShort();
+             }
+             //int startRangeOffset = in.getCurrentPos();
+             for (int i = 0; i < (cmapSegCountX2 / 2); i++) {
+                 cmapRangeOffsets[i] = fontFile.readTTFUShort();
+             }
+             int glyphIdArrayOffset = fontFile.getCurrentPos();
+             BitSet eightBitGlyphs = new BitSet(256);
+             // Insert the unicode id for the glyphs in mtxTab
+             // and fill in the cmaps ArrayList
+             for (int i = 0; i < cmapStartCounts.length; i++) {
+                 if (log.isTraceEnabled()) {
+                     log.trace(i + ": " + cmapStartCounts[i]
+                                                          + " - " + cmapEndCounts[i]);
+                 }
+                 if (log.isDebugEnabled()) {
+                     if (isInPrivateUseArea(cmapStartCounts[i], cmapEndCounts[i])) {
+                         log.debug("Font contains glyphs in the Unicode private use area: "
+                                 + Integer.toHexString(cmapStartCounts[i]) + " - "
+                                 + Integer.toHexString(cmapEndCounts[i]));
+                     }
+                 }
+                 for (int j = cmapStartCounts[i]; j <= cmapEndCounts[i]; j++) {
+                     // Update lastChar
+                     if (j < 256 && j > lastChar) {
+                         lastChar = (short)j;
+                     }
+                     if (j < 256) {
+                         eightBitGlyphs.set(j);
+                     }
+                     if (mtxPtr < mtxTab.length) {
+                         int glyphIdx;
+                         // the last character 65535 = .notdef
+                         // may have a range offset
+                         if (cmapRangeOffsets[i] != 0 && j != 65535) {
+                             int glyphOffset = glyphIdArrayOffset
+                                 + ((cmapRangeOffsets[i] / 2)
+                                     + (j - cmapStartCounts[i])
+                                     + (i)
+                                     - cmapSegCountX2 / 2) * 2;
+                             fontFile.seekSet(glyphOffset);
+                             glyphIdx = (fontFile.readTTFUShort() + cmapDeltas[i])
+                                        & 0xffff;
+                             //mtxTab[glyphIdx].setName(mtxTab[glyphIdx].getName() + " - "+(char)j);
+                             unicodeMappings.add(new UnicodeMapping(glyphIdx, j));
+                             mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(j));
+                             if (encodingID == 0 && j >= 0xF020 && j <= 0xF0FF) {
+                                 //Experimental: Mapping 0xF020-0xF0FF to 0x0020-0x00FF
+                                 //Tested with Wingdings and Symbol TTF fonts which map their
+                                 //glyphs in the region 0xF020-0xF0FF.
+                                 int mapped = j - 0xF000;
+                                 if (!eightBitGlyphs.get(mapped)) {
+                                     //Only map if Unicode code point hasn't been mapped before
+                                     unicodeMappings.add(new UnicodeMapping(glyphIdx, mapped));
+                                     mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(mapped));
+                                 }
+                             }
+                             // Also add winAnsiWidth
+                             List<Integer> v = ansiIndex.get(new Integer(j));
+                             if (v != null) {
+                                 for (Integer aIdx : v) {
+                                     ansiWidth[aIdx.intValue()]
+                                         = mtxTab[glyphIdx].getWx();
+                                     if (log.isTraceEnabled()) {
+                                         log.trace("Added width "
+                                                 + mtxTab[glyphIdx].getWx()
+                                                 + " uni: " + j
+                                                 + " ansi: " + aIdx.intValue());
+                                     }
+                                 }
+                             }
+                             if (log.isTraceEnabled()) {
+                                 log.trace("Idx: "
+                                         + glyphIdx
+                                         + " Delta: " + cmapDeltas[i]
+                                         + " Unicode: " + j
+                                         + " name: " + mtxTab[glyphIdx].getName());
+                             }
+                         } else {
+                             glyphIdx = (j + cmapDeltas[i]) & 0xffff;
+                             if (glyphIdx < mtxTab.length) {
+                                 mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(j));
+                             } else {
+                                 log.debug("Glyph " + glyphIdx
+                                                    + " out of range: "
+                                                    + mtxTab.length);
+                             }
+                             unicodeMappings.add(new UnicodeMapping(glyphIdx, j));
+                             if (glyphIdx < mtxTab.length) {
+                                 mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(j));
+                             } else {
+                                 log.debug("Glyph " + glyphIdx
+                                                    + " out of range: "
+                                                    + mtxTab.length);
+                             }
+                             // Also add winAnsiWidth
+                             List<Integer> v = ansiIndex.get(new Integer(j));
+                             if (v != null) {
+                                 for (Integer aIdx : v) {
+                                     ansiWidth[aIdx.intValue()] = mtxTab[glyphIdx].getWx();
+                                 }
+                             }
+                             //getLogger().debug("IIdx: " +
+                             //    mtxPtr +
+                             //    " Delta: " + cmap_deltas[i] +
+                             //    " Unicode: " + j +
+                             //    " name: " +
+                             //    mtxTab[(j+cmap_deltas[i]) & 0xffff].name);
+                         }
+                         if (glyphIdx < mtxTab.length) {
+                             if (mtxTab[glyphIdx].getUnicodeIndex().size() < 2) {
+                                 mtxPtr++;
+                             }
+                         }
+                     }
+                 }
+             }
+         } else {
+             log.error("Cmap format not supported: " + cmapFormat);
+             return false;
+         }
+         return true;
+     }
+     private boolean isInPrivateUseArea(int start, int end) {
+         return (isInPrivateUseArea(start) || isInPrivateUseArea(end));
+     }
+     private boolean isInPrivateUseArea(int unicode) {
+         return (unicode >= 0xE000 && unicode <= 0xF8FF);
+     }
+     /**
+      *
+      * @return mmtx data
+      */
+     public List<OFMtxEntry> getMtx() {
+         return Collections.unmodifiableList(Arrays.asList(mtxTab));
+     }
+     /**
+      * Print first char/last char
+      */
+     private void printMaxMin() {
+         int min = 255;
+         int max = 0;
+         for (int i = 0; i < mtxTab.length; i++) {
+             if (mtxTab[i].getIndex() < min) {
+                 min = mtxTab[i].getIndex();
+             }
+             if (mtxTab[i].getIndex() > max) {
+                 max = mtxTab[i].getIndex();
+             }
+         }
+         log.info("Min: " + min);
+         log.info("Max: " + max);
+     }
+     /**
+      * Reads the font using a FontFileReader.
+      *
+      * @param in The FontFileReader to use
+      * @throws IOException In case of an I/O problem
+      */
+     public void readFont(FontFileReader in, String header) throws IOException {
+         readFont(in, header, (String)null);
+     }
+     /**
+      * initialize the ansiWidths array (for winAnsiEncoding)
+      * and fill with the missingwidth
+      */
+     protected void initAnsiWidths() {
+         ansiWidth = new int[256];
+         for (int i = 0; i < 256; i++) {
+             ansiWidth[i] = mtxTab[0].getWx();
+         }
+         // Create an index hash to the ansiWidth
+         // Can't just index the winAnsiEncoding when inserting widths
+         // same char (eg bullet) is repeated more than one place
+         ansiIndex = new HashMap<Integer, List<Integer>>();
+         for (int i = 32; i < Glyphs.WINANSI_ENCODING.length; i++) {
+             Integer ansi = new Integer(i);
+             Integer uni = new Integer(Glyphs.WINANSI_ENCODING[i]);
+             List<Integer> v = ansiIndex.get(uni);
+             if (v == null) {
+                 v = new ArrayList<Integer>();
+                 ansiIndex.put(uni, v);
+             }
+             v.add(ansi);
+         }
+     }
+     /**
+      * Read the font data.
+      * If the fontfile is a TrueType Collection (.ttc file)
+      * the name of the font to read data for must be supplied,
+      * else the name is ignored.
+      *
+      * @param in The FontFileReader to use
+      * @param name The name of the font
+      * @return boolean Returns true if the font is valid
+      * @throws IOException In case of an I/O problem
+      */
+     public boolean readFont(FontFileReader in, String header, String name) throws IOException {
+         initializeFont(in);
+         /*
+          * Check if TrueType collection, and that the name
+          * exists in the collection
+          */
+         if (!checkTTC(header, name)) {
+             if (name == null) {
+                 throw new IllegalArgumentException(
+                     "For TrueType collection you must specify which font "
+                     + "to select (-ttcname)");
+             } else {
+                 throw new IOException(
+                     "Name does not exist in the TrueType collection: " + name);
+             }
+         }
+         readDirTabs();
+         readFontHeader();
+         getNumGlyphs();
+         if (log.isDebugEnabled()) {
+             log.debug("Number of glyphs in font: " + numberOfGlyphs);
+         }
+         readHorizontalHeader();
+         readHorizontalMetrics();
+         initAnsiWidths();
+         readPostScript();
+         readOS2();
+         determineAscDesc();
+         readName();
+         boolean pcltFound = readPCLT();
+         // Read cmap table and fill in ansiwidths
+         boolean valid = readCMAP();
+         if (!valid) {
+             return false;
+         }
+         // Create cmaps for bfentries
+         createCMaps();
+         updateBBoxAndOffset();
+         if (useKerning) {
+             readKerning();
+         }
+         handleCharacterSpacing(in);
+         guessVerticalMetricsFromGlyphBBox();
+         return true;
+     }
+     /**
+      * Reads a font.
+      *
+      * @param in FontFileReader to read from
+      * @param name Name to be checked for in the font file
+      * @param glyphs Map of glyphs (glyphs has old index as (Integer) key and
+      * new index as (Integer) value)
+      * @throws IOException in case of an I/O problem
+      */
+     public void readFont(FontFileReader in, String header, MultiByteFont mbfont) throws IOException {
+         readFont(in, header, mbfont.getTTCName());
+     }
+     protected abstract void updateBBoxAndOffset() throws IOException;
+     protected abstract void readName() throws IOException;
+     protected abstract void initializeFont(FontFileReader in) throws IOException;
+     protected void handleCharacterSpacing(FontFileReader in) throws IOException {
+         // Read advanced typographic tables.
+         if (useAdvanced) {
+             try {
+                 OTFAdvancedTypographicTableReader atr
+                     = new OTFAdvancedTypographicTableReader(this, in);
+                 atr.readAll();
+                 this.advancedTableReader = atr;
+             } catch (AdvancedTypographicTableFormatException e) {
+                 log.warn(
+                     "Encountered format constraint violation in advanced (typographic) table (AT) "
+                     + "in font '" + getFullName() + "', ignoring AT data: "
+                     + e.getMessage()
+                 );
+             }
+         }
+     }
+     protected void createCMaps() {
+         cmaps = new ArrayList<CMapSegment>();
+         int unicodeStart;
+         int glyphStart;
+         int unicodeEnd;
+         Iterator<UnicodeMapping> e = unicodeMappings.iterator();
+         UnicodeMapping um = e.next();
+         UnicodeMapping lastMapping = um;
+         unicodeStart = um.getUnicodeIndex();
+         glyphStart = um.getGlyphIndex();
+         while (e.hasNext()) {
+             um = e.next();
+             if (((lastMapping.getUnicodeIndex() + 1) != um.getUnicodeIndex())
+                     || ((lastMapping.getGlyphIndex() + 1) != um.getGlyphIndex())) {
+                 unicodeEnd = lastMapping.getUnicodeIndex();
+                 cmaps.add(new CMapSegment(unicodeStart, unicodeEnd, glyphStart));
+                 unicodeStart = um.getUnicodeIndex();
+                 glyphStart = um.getGlyphIndex();
+             }
+             lastMapping = um;
+         }
+         unicodeEnd = lastMapping.getUnicodeIndex();
+         cmaps.add(new CMapSegment(unicodeStart, unicodeEnd, glyphStart));
+     }
+     /**
+      * Returns the PostScript name of the font.
+      * @return String The PostScript name
+      */
+     public String getPostScriptName() {
+         if (postScriptName.length() == 0) {
+             return FontUtil.stripWhiteSpace(getFullName());
+         } else {
+             return postScriptName;
+         }
+     }
+     PostScriptVersion getPostScriptVersion() {
+         return postScriptVersion;
+     }
+     /**
+      * Returns the font family names of the font.
+      * @return Set The family names (a Set of Strings)
+      */
+     public Set<String> getFamilyNames() {
+         return familyNames;
+     }
+     /**
+      * Returns the font sub family name of the font.
+      * @return String The sub family name
+      */
+     public String getSubFamilyName() {
+         return subFamilyName;
+     }
+     /**
+      * Returns the full name of the font.
+      * @return String The full name
+      */
+     public String getFullName() {
+         return fullName;
+     }
+     /**
+      * Returns the name of the character set used.
+      * @return String The caracter set
+      */
+     public String getCharSetName() {
+         return ENCODING;
+     }
+     /**
+      * Returns the CapHeight attribute of the font.
+      * @return int The CapHeight
+      */
+     public int getCapHeight() {
+         return convertTTFUnit2PDFUnit(capHeight);
+     }
+     /**
+      * Returns the XHeight attribute of the font.
+      * @return int The XHeight
+      */
+     public int getXHeight() {
+         return convertTTFUnit2PDFUnit(xHeight);
+     }
+     /**
+      * Returns the number of bytes necessary to pad the currentPosition so that a table begins
+      * on a 4-byte boundary.
+      * @param currentPosition the position to pad.
+      * @return int the number of bytes to pad.
+      */
+     protected int getPadSize(int currentPosition) {
+         int padSize = 4 - (currentPosition % 4);
+         return padSize < 4 ? padSize : 0;
+     }
+     /**
+      * Returns the Flags attribute of the font.
+      * @return int The Flags
+      */
+     public int getFlags() {
+         int flags = 32;    // Use Adobe Standard charset
+         if (italicAngle != 0) {
+             flags |=  64;
+         }
+         if (isFixedPitch != 0) {
+             flags |=  2;
+         }
+         if (hasSerifs) {
+             flags |= 1;
+         }
+         return flags;
+     }
+     /**
+      * Returns the weight class of this font. Valid values are 100, 200....,800, 900.
+      * @return the weight class value (or 0 if there was no OS/2 table in the font)
+      */
+     public int getWeightClass() {
+         return this.usWeightClass;
+     }
+     /**
+      * Returns the StemV attribute of the font.
+      * @return String The StemV
+      */
+     public String getStemV() {
+         return "0";
+     }
+     /**
+      * Returns the ItalicAngle attribute of the font.
+      * @return String The ItalicAngle
+      */
+     public String getItalicAngle() {
+         String ia = Short.toString((short)(italicAngle / 0x10000));
+         // This is the correct italic angle, however only int italic
+         // angles are supported at the moment so this is commented out.
+         /*
+          * if ((italicAngle % 0x10000) > 0 )
+          * ia=ia+(comma+Short.toString((short)((short)((italicAngle % 0x10000)*1000)/0x10000)));
+          */
+         return ia;
+     }
+     /**
+      * @return int[] The font bbox
+      */
+     public int[] getFontBBox() {
+         final int[] fbb = new int[4];
+         fbb[0] = convertTTFUnit2PDFUnit(fontBBox1);
+         fbb[1] = convertTTFUnit2PDFUnit(fontBBox2);
+         fbb[2] = convertTTFUnit2PDFUnit(fontBBox3);
+         fbb[3] = convertTTFUnit2PDFUnit(fontBBox4);
+         return fbb;
+     }
+     /**
+      * Returns the LowerCaseAscent attribute of the font.
+      * @return int The LowerCaseAscent
+      */
+     public int getLowerCaseAscent() {
+         return convertTTFUnit2PDFUnit(ascender);
+     }
+     /**
+      * Returns the LowerCaseDescent attribute of the font.
+      * @return int The LowerCaseDescent
+      */
+     public int getLowerCaseDescent() {
+         return convertTTFUnit2PDFUnit(descender);
+     }
+     /**
+      * Returns the index of the last character, but this is for WinAnsiEncoding
+      * only, so the last char is < 256.
+      * @return short Index of the last character (<256)
+      */
+     public short getLastChar() {
+         return lastChar;
+     }
+     /**
+      * Returns the index of the first character.
+      * @return short Index of the first character
+      */
+     public short getFirstChar() {
+         return FIRST_CHAR;
+     }
+     /**
+      * Returns an array of character widths.
+      * @return int[] The character widths
+      */
+     public int[] getWidths() {
+         int[] wx = new int[mtxTab.length];
+         for (int i = 0; i < wx.length; i++) {
+             wx[i] = convertTTFUnit2PDFUnit(mtxTab[i].getWx());
+         }
 -        //underlinePosition
 -        fontFile.readTTFShort();
 -        //underlineThickness
 -        fontFile.readTTFShort();
+         return wx;
+     }
++    public Rectangle[] getBoundingBoxes() {
++        Rectangle[] boundingBoxes = new Rectangle[mtxTab.length];
++        for (int i = 0; i < boundingBoxes.length; i++) {
++            int[] boundingBox = mtxTab[i].getBoundingBox();
++            boundingBoxes[i] = new Rectangle(
++                    convertTTFUnit2PDFUnit(boundingBox[0]),
++                    convertTTFUnit2PDFUnit(boundingBox[1]),
++                    convertTTFUnit2PDFUnit(boundingBox[2] - boundingBox[0]),
++                    convertTTFUnit2PDFUnit(boundingBox[3] - boundingBox[1]));
++        }
++        return boundingBoxes;
++    }
++
+     /**
+      * Returns an array (xMin, yMin, xMax, yMax) for a glyph.
+      *
+      * @param glyphIndex the index of the glyph
+      * @return int[] Array defining bounding box.
+      */
+     public int[] getBBox(int glyphIndex) {
+         int[] bboxInTTFUnits = mtxTab[glyphIndex].getBoundingBox();
+         int[] bbox = new int[4];
+         for (int i = 0; i < 4; i++) {
+             bbox[i] = convertTTFUnit2PDFUnit(bboxInTTFUnits[i]);
+         }
+         return bbox;
+     }
+     /**
+      * Returns the width of a given character.
+      * @param idx Index of the character
+      * @return int Standard width
+      */
+     public int getCharWidth(int idx) {
+         return convertTTFUnit2PDFUnit(ansiWidth[idx]);
+     }
+     /**
+      * Returns the kerning table.
+      * @return Map The kerning table
+      */
+     public Map<Integer, Map<Integer, Integer>> getKerning() {
+         return kerningTab;
+     }
+     /**
+      * Returns the ANSI kerning table.
+      * @return Map The ANSI kerning table
+      */
+     public Map<Integer, Map<Integer, Integer>> getAnsiKerning() {
+         return ansiKerningTab;
+     }
++    public int getUnderlinePosition() {
++        return convertTTFUnit2PDFUnit(underlinePosition);
++    }
++
++    public int getUnderlineThickness() {
++        return convertTTFUnit2PDFUnit(underlineThickness);
++    }
++
++    public int getStrikeoutPosition() {
++        return convertTTFUnit2PDFUnit(strikeoutPosition);
++    }
++
++    public int getStrikeoutThickness() {
++        return convertTTFUnit2PDFUnit(strikeoutThickness);
++    }
++
+     /**
+      * Indicates if the font may be embedded.
+      * @return boolean True if it may be embedded
+      */
+     public boolean isEmbeddable() {
+         return isEmbeddable;
+     }
+     /**
+      * Indicates whether or not the font is an OpenType
+      * CFF font (rather than a TrueType font).
+      * @return true if the font is in OpenType CFF format.
+      */
+     public boolean isCFF() {
+        return this.isCFF;
+     }
+     /**
+      * Read Table Directory from the current position in the
+      * FontFileReader and fill the global HashMap dirTabs
+      * with the table name (String) as key and a TTFDirTabEntry
+      * as value.
+      * @throws IOException in case of an I/O problem
+      */
+     protected void readDirTabs() throws IOException {
+         int sfntVersion = fontFile.readTTFLong(); // TTF_FIXED_SIZE (4 bytes)
+         switch (sfntVersion) {
+         case 0x10000:
+             log.debug("sfnt version: OpenType 1.0");
+             break;
+         case 0x4F54544F: //"OTTO"
+             this.isCFF = true;
+             log.debug("sfnt version: OpenType with CFF data");
+             break;
+         case 0x74727565: //"true"
+             log.debug("sfnt version: Apple TrueType");
+             break;
+         case 0x74797031: //"typ1"
+             log.debug("sfnt version: Apple Type 1 housed in sfnt wrapper");
+             break;
+         default:
+             log.debug("Unknown sfnt version: " + Integer.toHexString(sfntVersion));
+             break;
+         }
+         int ntabs = fontFile.readTTFUShort();
+         fontFile.skip(6);    // 3xTTF_USHORT_SIZE
+         dirTabs = new HashMap<OFTableName, OFDirTabEntry>();
+         OFDirTabEntry[] pd = new OFDirTabEntry[ntabs];
+         log.debug("Reading " + ntabs + " dir tables");
+         for (int i = 0; i < ntabs; i++) {
+             pd[i] = new OFDirTabEntry();
+             String tableName = pd[i].read(fontFile);
+             dirTabs.put(OFTableName.getValue(tableName), pd[i]);
+         }
+         dirTabs.put(OFTableName.TABLE_DIRECTORY,
+                 new OFDirTabEntry(0L, fontFile.getCurrentPos()));
+         log.debug("dir tables: " + dirTabs.keySet());
+     }
+     /**
+      * Read the "head" table, this reads the bounding box and
+      * sets the upem (unitsPerEM) variable
+      * @throws IOException in case of an I/O problem
+      */
+     protected void readFontHeader() throws IOException {
+         seekTab(fontFile, OFTableName.HEAD, 2 * 4 + 2 * 4);
+         int flags = fontFile.readTTFUShort();
+         if (log.isDebugEnabled()) {
+             log.debug("flags: " + flags + " - " + Integer.toString(flags, 2));
+         }
+         upem = fontFile.readTTFUShort();
+         if (log.isDebugEnabled()) {
+             log.debug("unit per em: " + upem);
+         }
+         fontFile.skip(16);
+         fontBBox1 = fontFile.readTTFShort();
+         fontBBox2 = fontFile.readTTFShort();
+         fontBBox3 = fontFile.readTTFShort();
+         fontBBox4 = fontFile.readTTFShort();
+         if (log.isDebugEnabled()) {
+             log.debug("font bbox: xMin=" + fontBBox1
+                     + " yMin=" + fontBBox2
+                     + " xMax=" + fontBBox3
+                     + " yMax=" + fontBBox4);
+         }
+         fontFile.skip(2 + 2 + 2);
+         locaFormat = fontFile.readTTFShort();
+     }
+     /**
+      * Read the number of glyphs from the "maxp" table
+      * @throws IOException in case of an I/O problem
+      */
+     protected void getNumGlyphs() throws IOException {
+         seekTab(fontFile, OFTableName.MAXP, 4);
+         numberOfGlyphs = fontFile.readTTFUShort();
+     }
+     /**
+      * Read the "hhea" table to find the ascender and descender and
+      * size of "hmtx" table, as a fixed size font might have only
+      * one width.
+      * @throws IOException in case of an I/O problem
+      */
+     protected void readHorizontalHeader()
+             throws IOException {
+         seekTab(fontFile, OFTableName.HHEA, 4);
+         hheaAscender = fontFile.readTTFShort();
+         hheaDescender = fontFile.readTTFShort();
+         fontFile.skip(2 + 2 + 3 * 2 + 8 * 2);
+         nhmtx = fontFile.readTTFUShort();
+         if (log.isDebugEnabled()) {
+             log.debug("hhea.Ascender: " + formatUnitsForDebug(hheaAscender));
+             log.debug("hhea.Descender: " + formatUnitsForDebug(hheaDescender));
+             log.debug("Number of horizontal metrics: " + nhmtx);
+         }
+     }
+     /**
+      * Read "hmtx" table and put the horizontal metrics
+      * in the mtxTab array. If the number of metrics is less
+      * than the number of glyphs (eg fixed size fonts), extend
+      * the mtxTab array and fill in the missing widths
+      * @throws IOException in case of an I/O problem
+      */
+     protected void readHorizontalMetrics()
+             throws IOException {
+         seekTab(fontFile, OFTableName.HMTX, 0);
+         int mtxSize = Math.max(numberOfGlyphs, nhmtx);
+         mtxTab = new OFMtxEntry[mtxSize];
+         if (log.isTraceEnabled()) {
+             log.trace("*** Widths array: \n");
+         }
+         for (int i = 0; i < mtxSize; i++) {
+             mtxTab[i] = new OFMtxEntry();
+         }
+         for (int i = 0; i < nhmtx; i++) {
+             mtxTab[i].setWx(fontFile.readTTFUShort());
+             mtxTab[i].setLsb(fontFile.readTTFUShort());
+             if (log.isTraceEnabled()) {
+                 log.trace("   width[" + i + "] = "
+                           + convertTTFUnit2PDFUnit(mtxTab[i].getWx()) + ";");
+             }
+         }
+         if (nhmtx < mtxSize) {
+             // Fill in the missing widths
+             int lastWidth = mtxTab[nhmtx - 1].getWx();
+             for (int i = nhmtx; i < mtxSize; i++) {
+                 mtxTab[i].setWx(lastWidth);
+                 mtxTab[i].setLsb(fontFile.readTTFUShort());
+             }
+         }
+     }
+     /**
+      * Read the "post" table
+      * containing the PostScript names of the glyphs.
+      */
+     protected void readPostScript() throws IOException {
+         seekTab(fontFile, OFTableName.POST, 0);
+         int postFormat = fontFile.readTTFLong();
+         italicAngle = fontFile.readTTFULong();
 -            fontFile.skip(11 * 2);
++        underlinePosition = fontFile.readTTFShort();
++        underlineThickness = fontFile.readTTFShort();
+         isFixedPitch = fontFile.readTTFULong();
+         //Skip memory usage values
+         fontFile.skip(4 * 4);
+         log.debug("PostScript format: 0x" + Integer.toHexString(postFormat));
+         switch (postFormat) {
+         case 0x00010000:
+             log.debug("PostScript format 1");
+             postScriptVersion = PostScriptVersion.V1;
+             for (int i = 0; i < MAC_GLYPH_ORDERING.length; i++) {
+                 mtxTab[i].setName(MAC_GLYPH_ORDERING[i]);
+             }
+             break;
+         case 0x00020000:
+             log.debug("PostScript format 2");
+             postScriptVersion = PostScriptVersion.V2;
+             int numGlyphStrings = 0;
+             // Read Number of Glyphs
+             int l = fontFile.readTTFUShort();
+             // Read indexes
+             for (int i = 0; i < l; i++) {
+                 mtxTab[i].setIndex(fontFile.readTTFUShort());
+                 if (mtxTab[i].getIndex() > 257) {
+                     //Index is not in the Macintosh standard set
+                     numGlyphStrings++;
+                 }
+                 if (log.isTraceEnabled()) {
+                     log.trace("PostScript index: " + mtxTab[i].getIndexAsString());
+                 }
+             }
+             // firstChar=minIndex;
+             String[] psGlyphsBuffer = new String[numGlyphStrings];
+             if (log.isDebugEnabled()) {
+                 log.debug("Reading " + numGlyphStrings
+                         + " glyphnames, that are not in the standard Macintosh"
+                         + " set. Total number of glyphs=" + l);
+             }
+             for (int i = 0; i < psGlyphsBuffer.length; i++) {
+                 psGlyphsBuffer[i] = fontFile.readTTFString(fontFile.readTTFUByte());
+             }
+             //Set glyph names
+             for (int i = 0; i < l; i++) {
+                 if (mtxTab[i].getIndex() < MAC_GLYPH_ORDERING.length) {
+                     mtxTab[i].setName(MAC_GLYPH_ORDERING[mtxTab[i].getIndex()]);
+                 } else {
+                     if (!mtxTab[i].isIndexReserved()) {
+                         int k = mtxTab[i].getIndex() - MAC_GLYPH_ORDERING.length;
+                         if (log.isTraceEnabled()) {
+                             log.trace(k + " i=" + i + " mtx=" + mtxTab.length
+                                 + " ps=" + psGlyphsBuffer.length);
+                         }
+                         mtxTab[i].setName(psGlyphsBuffer[k]);
+                     }
+                 }
+             }
+             break;
+         case 0x00030000:
+             // PostScript format 3 contains no glyph names
+             log.debug("PostScript format 3");
+             postScriptVersion = PostScriptVersion.V3;
+             break;
+         default:
+             log.error("Unknown PostScript format: " + postFormat);
+             postScriptVersion = PostScriptVersion.UNKNOWN;
+         }
+     }
+     /**
+      * Read the "OS/2" table
+      */
+     protected void readOS2() throws IOException {
+         // Check if font is embeddable
+         OFDirTabEntry os2Entry = dirTabs.get(OFTableName.OS2);
+         if (os2Entry != null) {
+             seekTab(fontFile, OFTableName.OS2, 0);
+             int version = fontFile.readTTFUShort();
+             if (log.isDebugEnabled()) {
+                 log.debug("OS/2 table: version=" + version
+                         + ", offset=" + os2Entry.getOffset() + ", len=" + os2Entry.getLength());
+             }
+             fontFile.skip(2); //xAvgCharWidth
+             this.usWeightClass = fontFile.readTTFUShort();
+             // usWidthClass
+             fontFile.skip(2);
+             int fsType = fontFile.readTTFUShort();
+             if (fsType == 2) {
+                 isEmbeddable = false;
+             } else {
+                 isEmbeddable = true;
+             }
++            fontFile.skip(8 * 2);
++            strikeoutThickness = fontFile.readTTFShort();
++            strikeoutPosition = fontFile.readTTFShort();
++            fontFile.skip(2);
+             fontFile.skip(10); //panose array
+             fontFile.skip(4 * 4); //unicode ranges
+             fontFile.skip(4);
+             fontFile.skip(3 * 2);
+             int v;
+             os2Ascender = fontFile.readTTFShort(); //sTypoAscender
+             os2Descender = fontFile.readTTFShort(); //sTypoDescender
+             if (log.isDebugEnabled()) {
+                 log.debug("sTypoAscender: " + os2Ascender
+                         + " -> internal " + convertTTFUnit2PDFUnit(os2Ascender));
+                 log.debug("sTypoDescender: " + os2Descender
+                         + " -> internal " + convertTTFUnit2PDFUnit(os2Descender));
+             }
+             v = fontFile.readTTFShort(); //sTypoLineGap
+             if (log.isDebugEnabled()) {
+                 log.debug("sTypoLineGap: " + v);
+             }
+             v = fontFile.readTTFUShort(); //usWinAscent
+             if (log.isDebugEnabled()) {
+                 log.debug("usWinAscent: " + formatUnitsForDebug(v));
+             }
+             v = fontFile.readTTFUShort(); //usWinDescent
+             if (log.isDebugEnabled()) {
+                 log.debug("usWinDescent: " + formatUnitsForDebug(v));
+             }
+             //version 1 OS/2 table might end here
+             if (os2Entry.getLength() >= 78 + (2 * 4) + (2 * 2)) {
+                 fontFile.skip(2 * 4);
+                 this.os2xHeight = fontFile.readTTFShort(); //sxHeight
+                 this.os2CapHeight = fontFile.readTTFShort(); //sCapHeight
+                 if (log.isDebugEnabled()) {
+                     log.debug("sxHeight: " + this.os2xHeight);
+                     log.debug("sCapHeight: " + this.os2CapHeight);
+                 }
+             }
+         } else {
+             isEmbeddable = true;
+         }
+     }
+     /**
+      * Read the "PCLT" table to find xHeight and capHeight.
+      * @throws IOException In case of a I/O problem
+      */
+     protected boolean readPCLT() throws IOException {
+         OFDirTabEntry dirTab = dirTabs.get(OFTableName.PCLT);
+         if (dirTab != null) {
+             fontFile.seekSet(dirTab.getOffset() + 4 + 4 + 2);
+             xHeight = fontFile.readTTFUShort();
+             log.debug("xHeight from PCLT: " + formatUnitsForDebug(xHeight));
+             fontFile.skip(2 * 2);
+             capHeight = fontFile.readTTFUShort();
+             log.debug("capHeight from PCLT: " + formatUnitsForDebug(capHeight));
+             fontFile.skip(2 + 16 + 8 + 6 + 1 + 1);
+             int serifStyle = fontFile.readTTFUByte();
+             serifStyle = serifStyle >> 6;
+             serifStyle = serifStyle & 3;
+             if (serifStyle == 1) {
+                 hasSerifs = false;
+             } else {
+                 hasSerifs = true;
+             }
+             return true;
+         } else {
+             return false;
+         }
+     }
+     /**
+      * Determines the right source for the ascender and descender values. The problem here is
+      * that the interpretation of these values is not the same for every font. There doesn't seem
+      * to be a uniform definition of an ascender and a descender. In some fonts
+      * the hhea values are defined after the Apple interpretation, but not in every font. The
+      * same problem is in the OS/2 table. FOP needs the ascender and descender to determine the
+      * baseline so we need values which add up more or less to the "em box". However, due to
+      * accent modifiers a character can grow beyond the em box.
+      */
+     protected void determineAscDesc() {
+         int hheaBoxHeight = hheaAscender - hheaDescender;
+         int os2BoxHeight = os2Ascender - os2Descender;
+         if (os2Ascender > 0 && os2BoxHeight <= upem) {
+             ascender = os2Ascender;
+             descender = os2Descender;
+         } else if (hheaAscender > 0 && hheaBoxHeight <= upem) {
+             ascender = hheaAscender;
+             descender = hheaDescender;
+         } else {
+             if (os2Ascender > 0) {
+                 //Fall back to info from OS/2 if possible
+                 ascender = os2Ascender;
+                 descender = os2Descender;
+             } else {
+                 ascender = hheaAscender;
+                 descender = hheaDescender;
+             }
+         }
+         if (log.isDebugEnabled()) {
+             log.debug("Font box height: " + (ascender - descender));
+             if (ascender - descender > upem) {
+                 log.debug("Ascender and descender together are larger than the em box.");
+             }
+         }
+     }
+     protected void guessVerticalMetricsFromGlyphBBox() {
+         // Approximate capHeight from height of "H"
+         // It's most unlikely that a font misses the PCLT table
+         // This also assumes that postscriptnames exists ("H")
+         // Should look it up in the cmap (that wouldn't help
+         // for charsets without H anyway...)
+         // Same for xHeight with the letter "x"
+         int localCapHeight = 0;
+         int localXHeight = 0;
+         int localAscender = 0;
+         int localDescender = 0;
+         for (int i = 0; i < mtxTab.length; i++) {
+             if ("H".equals(mtxTab[i].getName())) {
+                 localCapHeight = mtxTab[i].getBoundingBox()[3];
+             } else if ("x".equals(mtxTab[i].getName())) {
+                 localXHeight = mtxTab[i].getBoundingBox()[3];
+             } else if ("d".equals(mtxTab[i].getName())) {
+                 localAscender = mtxTab[i].getBoundingBox()[3];
+             } else if ("p".equals(mtxTab[i].getName())) {
+                 localDescender = mtxTab[i].getBoundingBox()[1];
+             } else {
+                 // OpenType Fonts with a version 3.0 "post" table don't have glyph names.
+                 // Use Unicode indices instead.
+                 List unicodeIndex = mtxTab[i].getUnicodeIndex();
+                 if (unicodeIndex.size() > 0) {
+                     //Only the first index is used
+                     char ch = (char)((Integer)unicodeIndex.get(0)).intValue();
+                     if (ch == 'H') {
+                         localCapHeight = mtxTab[i].getBoundingBox()[3];
+                     } else if (ch == 'x') {
+                         localXHeight = mtxTab[i].getBoundingBox()[3];
+                     } else if (ch == 'd') {
+                         localAscender = mtxTab[i].getBoundingBox()[3];
+                     } else if (ch == 'p') {
+                         localDescender = mtxTab[i].getBoundingBox()[1];
+                     }
+                 }
+             }
+         }
+         if (log.isDebugEnabled()) {
+             log.debug("Ascender from glyph 'd': " + formatUnitsForDebug(localAscender));
+             log.debug("Descender from glyph 'p': " + formatUnitsForDebug(localDescender));
+         }
+         if (ascender - descender > upem) {
+             log.debug("Replacing specified ascender/descender with derived values to get values"
+                     + " which fit in the em box.");
+             ascender = localAscender;
+             descender = localDescender;
+         }
+         if (log.isDebugEnabled()) {
+             log.debug("xHeight from glyph 'x': " + formatUnitsForDebug(localXHeight));
+             log.debug("CapHeight from glyph 'H': " + formatUnitsForDebug(localCapHeight));
+         }
+         if (capHeight == 0) {
+             capHeight = localCapHeight;
+             if (capHeight == 0) {
+                 capHeight = os2CapHeight;
+             }
+             if (capHeight == 0) {
+                 log.debug("capHeight value could not be determined."
+                         + " The font may not work as expected.");
+             }
+         }
+         if (xHeight == 0) {
+             xHeight = localXHeight;
+             if (xHeight == 0) {
+                 xHeight = os2xHeight;
+             }
+             if (xHeight == 0) {
+                 log.debug("xHeight value could not be determined."
+                         + " The font may not work as expected.");
+             }
+         }
+     }
+     /**
+      * Read the kerning table, create a table for both CIDs and
+      * winAnsiEncoding.
+      * @throws IOException In case of a I/O problem
+      */
+     protected void readKerning() throws IOException {
+         // Read kerning
+         kerningTab = new HashMap<Integer, Map<Integer, Integer>>();
+         ansiKerningTab = new HashMap<Integer, Map<Integer, Integer>>();
+         OFDirTabEntry dirTab = dirTabs.get(OFTableName.KERN);
+         if (dirTab != null) {
+             seekTab(fontFile, OFTableName.KERN, 2);
+             for (int n = fontFile.readTTFUShort(); n > 0; n--) {
+                 fontFile.skip(2 * 2);
+                 int k = fontFile.readTTFUShort();
+                 if (!((k & 1) != 0) || (k & 2) != 0 || (k & 4) != 0) {
+                     return;
+                 }
+                 if ((k >> 8) != 0) {
+                     continue;
+                 }
+                 k = fontFile.readTTFUShort();
+                 fontFile.skip(3 * 2);
+                 while (k-- > 0) {
+                     int i = fontFile.readTTFUShort();
+                     int j = fontFile.readTTFUShort();
+                     int kpx = fontFile.readTTFShort();
+                     if (kpx != 0) {
+                         // CID kerning table entry, using unicode indexes
+                         final Integer iObj = glyphToUnicode(i);
+                         final Integer u2 = glyphToUnicode(j);
+                         if (iObj == null) {
+                             // happens for many fonts (Ubuntu font set),
+                             // stray entries in the kerning table??
+                             log.debug("Ignoring kerning pair because no Unicode index was"
+                                     + " found for the first glyph " + i);
+                         } else if (u2 == null) {
+                             log.debug("Ignoring kerning pair because Unicode index was"
+                                     + " found for the second glyph " + i);
+                         } else {
+                             Map<Integer, Integer> adjTab = kerningTab.get(iObj);
+                             if (adjTab == null) {
+                                 adjTab = new HashMap<Integer, Integer>();
+                             }
+                             adjTab.put(u2, new Integer(convertTTFUnit2PDFUnit(kpx)));
+                             kerningTab.put(iObj, adjTab);
+                         }
+                     }
+                 }
+             }
+             // Create winAnsiEncoded kerning table from kerningTab
+             // (could probably be simplified, for now we remap back to CID indexes and
+             // then to winAnsi)
+             for (Integer unicodeKey1 : kerningTab.keySet()) {
+                 Integer cidKey1 = unicodeToGlyph(unicodeKey1.intValue());
+                 Map<Integer, Integer> akpx = new HashMap<Integer, Integer>();
+                 Map<Integer, Integer> ckpx = kerningTab.get(unicodeKey1);
+                 for (Integer unicodeKey2 : ckpx.keySet()) {
+                     Integer cidKey2 = unicodeToGlyph(unicodeKey2.intValue());
+                     Integer kern = ckpx.get(unicodeKey2);
+                     Iterator uniMap = mtxTab[cidKey2.intValue()].getUnicodeIndex().listIterator();
+                     while (uniMap.hasNext()) {
+                         Integer unicodeKey = (Integer)uniMap.next();
+                         Integer[] ansiKeys = unicodeToWinAnsi(unicodeKey.intValue());
+                         for (int u = 0; u < ansiKeys.length; u++) {
+                             akpx.put(ansiKeys[u], kern);
+                         }
+                     }
+                 }
+                 if (akpx.size() > 0) {
+                     Iterator uniMap = mtxTab[cidKey1.intValue()].getUnicodeIndex().listIterator();
+                     while (uniMap.hasNext()) {
+                         Integer unicodeKey = (Integer)uniMap.next();
+                         Integer[] ansiKeys = unicodeToWinAnsi(unicodeKey.intValue());
+                         for (int u = 0; u < ansiKeys.length; u++) {
+                             ansiKerningTab.put(ansiKeys[u], akpx);
+                         }
+                     }
+                 }
+             }
+         }
+     }
+     /**
+      * Streams a font.
+      * @param ttfOut The interface for streaming TrueType tables.
+      * @exception IOException file write error
+      */
+     public void stream(TTFOutputStream ttfOut) throws IOException {
+         SortedSet<Map.Entry<OFTableName, OFDirTabEntry>> sortedDirTabs = sortDirTabMap(dirTabs);
+         byte[] file = fontFile.getAllBytes();
+         TTFTableOutputStream tableOut = ttfOut.getTableOutputStream();
+         TTFGlyphOutputStream glyphOut = ttfOut.getGlyphOutputStream();
+         ttfOut.startFontStream();
+         for (Map.Entry<OFTableName, OFDirTabEntry> entry : sortedDirTabs) {
+             int offset = (int) entry.getValue().getOffset();
+             int paddedLength = (int) entry.getValue().getLength();
+             paddedLength += getPadSize(offset + paddedLength);
+             if (entry.getKey().equals(OFTableName.GLYF)) {
+                 streamGlyf(glyphOut, file, offset, paddedLength);
+             } else {
+                 tableOut.streamTable(file, offset, paddedLength);
+             }
+         }
+         ttfOut.endFontStream();
+     }
+     private void streamGlyf(TTFGlyphOutputStream glyphOut, byte[] fontFile, int tableOffset,
+             int tableLength) throws IOException {
+         //Stream all but the last glyph
+         int glyphStart = 0;
+         int glyphEnd = 0;
+         glyphOut.startGlyphStream();
+         for (int i = 0; i < mtxTab.length - 1; i++) {
+             glyphStart = (int) mtxTab[i].getOffset() + tableOffset;
+             glyphEnd = (int) mtxTab[i + 1].getOffset() + tableOffset;
+             glyphOut.streamGlyph(fontFile, glyphStart, glyphEnd - glyphStart);
+         }
+         glyphOut.streamGlyph(fontFile, glyphEnd, (tableOffset + tableLength) - glyphEnd);
+         glyphOut.endGlyphStream();
+     }
+     /**
+      * Returns the order in which the tables in a TrueType font should be written to file.
+      * @param directoryTabs the map that is to be sorted.
+      * @return TTFTablesNames[] an array of table names sorted in the order they should appear in
+      * the TTF file.
+      */
+     SortedSet<Map.Entry<OFTableName, OFDirTabEntry>>
+                         sortDirTabMap(Map<OFTableName, OFDirTabEntry> directoryTabs) {
+         SortedSet<Map.Entry<OFTableName, OFDirTabEntry>> sortedSet
+             = new TreeSet<Map.Entry<OFTableName, OFDirTabEntry>>(
+                     new Comparator<Map.Entry<OFTableName, OFDirTabEntry>>() {
+             public int compare(Entry<OFTableName, OFDirTabEntry> o1,
+                     Entry<OFTableName, OFDirTabEntry> o2) {
+                 return (int) (o1.getValue().getOffset() - o2.getValue().getOffset());
+             }
+         });
+         sortedSet.addAll(directoryTabs.entrySet());
+         return sortedSet;
+     }
+     /**
+      * Returns this font's character to glyph mapping.
+      *
+      * @return the font's cmap
+      */
+     public List<CMapSegment> getCMaps() {
+         return cmaps;
+     }
+     /**
+      * Check if this is a TrueType collection and that the given
+      * name exists in the collection.
+      * If it does, set offset in fontfile to the beginning of
+      * the Table Directory for that font.
+      * @param name The name to check
+      * @return True if not collection or font name present, false otherwise
+      * @throws IOException In case of an I/O problem
+      */
+     protected final boolean checkTTC(String tag, String name) throws IOException {
+         if ("ttcf".equals(tag)) {
+             // This is a TrueType Collection
+             fontFile.skip(4);
+             // Read directory offsets
+             int numDirectories = (int)fontFile.readTTFULong();
+             // int numDirectories=in.readTTFUShort();
+             long[] dirOffsets = new long[numDirectories];
+             for (int i = 0; i < numDirectories; i++) {
+                 dirOffsets[i] = fontFile.readTTFULong();
+             }
+             log.info("This is a TrueType collection file with "
+                                    + numDirectories + " fonts");
+             log.info("Containing the following fonts: ");
+             // Read all the directories and name tables to check
+             // If the font exists - this is a bit ugly, but...
+             boolean found = false;
+             // Iterate through all name tables even if font
+             // Is found, just to show all the names
+             long dirTabOffset = 0;
+             for (int i = 0; (i < numDirectories); i++) {
+                 fontFile.seekSet(dirOffsets[i]);
+                 readDirTabs();
+                 readName();
+                 if (fullName.equals(name)) {
+                     found = true;
+                     dirTabOffset = dirOffsets[i];
+                     log.info(fullName + " <-- selected");
+                 } else {
+                     log.info(fullName);
+                 }
+                 // Reset names
+                 notice = "";
+                 fullName = "";
+                 familyNames.clear();
+                 postScriptName = "";
+                 subFamilyName = "";
+             }
+             fontFile.seekSet(dirTabOffset);
+             return found;
+         } else {
+             fontFile.seekSet(0);
+             return true;
+         }
+     }
+     /**
+      * Return TTC font names
+      * @param in FontFileReader to read from
+      * @return True if not collection or font name present, false otherwise
+      * @throws IOException In case of an I/O problem
+      */
+     public final List<String> getTTCnames(FontFileReader in) throws IOException {
+         this.fontFile = in;
+         List<String> fontNames = new ArrayList<String>();
+         String tag = in.readTTFString(4);
+         if ("ttcf".equals(tag)) {
+             // This is a TrueType Collection
+             in.skip(4);
+             // Read directory offsets
+             int numDirectories = (int)in.readTTFULong();
+             long[] dirOffsets = new long[numDirectories];
+             for (int i = 0; i < numDirectories; i++) {
+                 dirOffsets[i] = in.readTTFULong();
+             }
+             log.info("This is a TrueType collection file with "
+                       + numDirectories + " fonts");
+             log.info("Containing the following fonts: ");
+             for (int i = 0; (i < numDirectories); i++) {
+                 in.seekSet(dirOffsets[i]);
+                 readDirTabs();
+                 readName();
+                 log.info(fullName);
+                 fontNames.add(fullName);
+                 // Reset names
+                 notice = "";
+                 fullName = "";
+                 familyNames.clear();
+                 postScriptName = "";
+                 subFamilyName = "";
+             }
+             in.seekSet(0);
+             return fontNames;
+         } else {
+             log.error("Not a TTC!");
+             return null;
+         }
+     }
+     /*
+      * Helper classes, they are not very efficient, but that really
+      * doesn't matter...
+      */
+     private Integer[] unicodeToWinAnsi(int unicode) {
+         List<Integer> ret = new ArrayList<Integer>();
+         for (int i = 32; i < Glyphs.WINANSI_ENCODING.length; i++) {
+             if (unicode == Glyphs.WINANSI_ENCODING[i]) {
+                 ret.add(new Integer(i));
+             }
+         }
+         return ret.toArray(new Integer[0]);
+     }
+     /**
+      * Dumps a few informational values to System.out.
+      */
+     public void printStuff() {
+         System.out.println("Font name:   " + postScriptName);
+         System.out.println("Full name:   " + fullName);
+         System.out.println("Family name: " + familyNames);
+         System.out.println("Subfamily name: " + subFamilyName);
+         System.out.println("Notice:      " + notice);
+         System.out.println("xHeight:     " + convertTTFUnit2PDFUnit(xHeight));
+         System.out.println("capheight:   " + convertTTFUnit2PDFUnit(capHeight));
+         int italic = (int)(italicAngle >> 16);
+         System.out.println("Italic:      " + italic);
+         System.out.print("ItalicAngle: " + (short)(italicAngle / 0x10000));
+         if ((italicAngle % 0x10000) > 0) {
+             System.out.print("."
+                              + (short)((italicAngle % 0x10000) * 1000)
+                                / 0x10000);
+         }
+         System.out.println();
+         System.out.println("Ascender:    " + convertTTFUnit2PDFUnit(ascender));
+         System.out.println("Descender:   " + convertTTFUnit2PDFUnit(descender));
+         System.out.println("FontBBox:    [" + convertTTFUnit2PDFUnit(fontBBox1)
+                            + " " + convertTTFUnit2PDFUnit(fontBBox2) + " "
+                            + convertTTFUnit2PDFUnit(fontBBox3) + " "
+                            + convertTTFUnit2PDFUnit(fontBBox4) + "]");
+     }
+     private String formatUnitsForDebug(int units) {
+         return units + " -> " + convertTTFUnit2PDFUnit(units) + " internal units";
+     }
+     /**
+      * Map a glyph index to the corresponding unicode code point
+      *
+      * @param glyphIndex
+      * @return unicode code point
+      */
+     private Integer glyphToUnicode(int glyphIndex) {
+         return glyphToUnicodeMap.get(new Integer(glyphIndex));
+     }
+     /**
+      * Map a unicode code point to the corresponding glyph index
+      *
+      * @param unicodeIndex unicode code point
+      * @return glyph index
+      */
+     private Integer unicodeToGlyph(int unicodeIndex) throws IOException {
+         final Integer result
+             = unicodeToGlyphMap.get(new Integer(unicodeIndex));
+         if (result == null) {
+             throw new IOException(
+                     "Glyph index not found for unicode value " + unicodeIndex);
+         }
+         return result;
+     }
+     String getGlyphName(int glyphIndex) {
+         return mtxTab[glyphIndex].getName();
+     }
+     /**
+      * Determine if advanced (typographic) table is present.
+      * @return true if advanced (typographic) table is present
+      */
+     public boolean hasAdvancedTable() {
+         if (advancedTableReader != null) {
+             return  advancedTableReader.hasAdvancedTable();
+         } else {
+             return false;
+         }
+     }
+     /**
+      * Returns the GDEF table or null if none present.
+      * @return the GDEF table
+      */
+     public GlyphDefinitionTable getGDEF() {
+         if (advancedTableReader != null) {
+             return  advancedTableReader.getGDEF();
+         } else {
+             return null;
+         }
+     }
+     /**
+      * Returns the GSUB table or null if none present.
+      * @return the GSUB table
+      */
+     public GlyphSubstitutionTable getGSUB() {
+         if (advancedTableReader != null) {
+             return  advancedTableReader.getGSUB();
+         } else {
+             return null;
+         }
+     }
+     /**
+      * Returns the GPOS table or null if none present.
+      * @return the GPOS table
+      */
+     public GlyphPositioningTable getGPOS() {
+         if (advancedTableReader != null) {
+             return  advancedTableReader.getGPOS();
+         } else {
+             return null;
+         }
+     }
+     /**
+      * Static main method to get info about a TrueType font.
+      * @param args The command line arguments
+      */
+     public static void main(String[] args) {
+         InputStream stream = null;
+         try {
+             boolean useKerning = true;
+             boolean useAdvanced = true;
+             stream = new FileInputStream(args[0]);
+             FontFileReader reader = new FontFileReader(stream);
+             String name = null;
+             if (args.length >= 2) {
+                 name = args[1];
+             }
+             String header = OFFontLoader.readHeader(reader);
+             boolean isCFF = header.equals("OTTO");
+             OpenFont otfFile = (isCFF) ? new OTFFile() : new TTFFile(useKerning, useAdvanced);
+             otfFile.readFont(reader, header, name);
+             otfFile.printStuff();
+         } catch (IOException ioe) {
+             System.err.println("Problem reading font: " + ioe.toString());
+             ioe.printStackTrace(System.err);
+         } finally {
+             IOUtils.closeQuietly(stream);
+         }
+     }
+ }
index bd8e970639258404bfa38ecc766654c4b62e899c,cadc282679f6875c51bda385e6e37d2a12ecd9ae..4d215926b5eaa55c6aa46e0faca1f61b03ee1b67
@@@ -39,9 -62,9 +62,10 @@@ import org.apache.fop.image.loader.bati
  import org.apache.fop.image.loader.batik.BatikUtil;
  import org.apache.fop.render.ImageHandler;
  import org.apache.fop.render.RenderingContext;
+ import org.apache.fop.render.ps.svg.PSSVGGraphics2D;
  import org.apache.fop.svg.SVGEventProducer;
  import org.apache.fop.svg.SVGUserAgent;
 +import org.apache.fop.svg.font.FOPFontFamilyResolverImpl;
  
  /**
   * Image handler implementation which handles SVG images for PostScript output.
@@@ -59,78 -85,262 +86,262 @@@ public class PSImageHandlerSVG implemen
          PSGenerator gen = psContext.getGenerator();
          ImageXMLDOM imageSVG = (ImageXMLDOM)image;
  
-         //Controls whether text painted by Batik is generated using text or path operations
-         boolean strokeText = false;
-         //TODO Configure text stroking
+         if (shouldRaster(imageSVG)) {
+             InputStream is = renderSVGToInputStream(context, imageSVG);
+             float x = (float) pos.getX() / 1000f;
+             float y = (float) pos.getY() / 1000f;
+             float w = (float) pos.getWidth() / 1000f;
+             float h = (float) pos.getHeight() / 1000f;
+             Rectangle2D targetRect = new Rectangle2D.Double(x, y, w, h);
+             MaskedImage mi = convertToRGB(ImageIO.read(is));
+             BufferedImage ri = mi.getImage();
+             ImageEncoder encoder = ImageEncodingHelper.createRenderedImageEncoder(ri);
+             Dimension imgDim = new Dimension(ri.getWidth(), ri.getHeight());
+             String imgDescription = ri.getClass().getName();
+             ImageEncodingHelper helper = new ImageEncodingHelper(ri);
+             ColorModel cm = helper.getEncodedColorModel();
+             PSImageUtils.writeImage(encoder, imgDim, imgDescription, targetRect, cm, gen, ri, mi.getMaskColor());
+         } else {
+             //Controls whether text painted by Batik is generated using text or path operations
+             boolean strokeText = false;
+             //TODO Configure text stroking
 -            SVGUserAgent ua
 -                 = new SVGUserAgent(context.getUserAgent(), new AffineTransform());
++            SVGUserAgent ua = new SVGUserAgent(context.getUserAgent(),
++                    new FOPFontFamilyResolverImpl(psContext.getFontInfo()), new AffineTransform());
+             PSSVGGraphics2D graphics = new PSSVGGraphics2D(strokeText, gen);
+             graphics.setGraphicContext(new org.apache.xmlgraphics.java2d.GraphicContext());
+             BridgeContext ctx = new PSBridgeContext(ua,
+                     (strokeText ? null : psContext.getFontInfo()),
+                     context.getUserAgent().getImageManager(),
+                     context.getUserAgent().getImageSessionContext());
+             //Cloning SVG DOM as Batik attaches non-thread-safe facilities (like the CSS engine)
+             //to it.
+             Document clonedDoc = BatikUtil.cloneSVGDocument(imageSVG.getDocument());
+             GraphicsNode root;
+             try {
+                 GVTBuilder builder = new GVTBuilder();
+                 root = builder.build(ctx, clonedDoc);
+             } catch (Exception e) {
+                 SVGEventProducer eventProducer = SVGEventProducer.Provider.get(
+                         context.getUserAgent().getEventBroadcaster());
+                 eventProducer.svgNotBuilt(this, e, image.getInfo().getOriginalURI());
+                 return;
+             }
+             // get the 'width' and 'height' attributes of the SVG document
+             float w = (float)ctx.getDocumentSize().getWidth() * 1000f;
+             float h = (float)ctx.getDocumentSize().getHeight() * 1000f;
  
-         SVGUserAgent ua = new SVGUserAgent(context.getUserAgent(),
-                 new FOPFontFamilyResolverImpl(psContext.getFontInfo()), new AffineTransform());
+             float sx = pos.width / w;
+             float sy = pos.height / h;
  
-         PSGraphics2D graphics = new PSGraphics2D(strokeText, gen);
-         graphics.setGraphicContext(new org.apache.xmlgraphics.java2d.GraphicContext());
+             ctx = null;
  
-         BridgeContext ctx = new PSBridgeContext(ua,
-                 (strokeText ? null : psContext.getFontInfo()),
-                 context.getUserAgent().getImageManager(),
-                 context.getUserAgent().getImageSessionContext());
+             gen.commentln("%FOPBeginSVG");
+             gen.saveGraphicsState();
+             final boolean clip = false;
+             if (clip) {
+                 /*
+                  * Clip to the svg area.
+                  * Note: To have the svg overlay (under) a text area then use
+                  * an fo:block-container
+                  */
+                 gen.writeln("newpath");
+                 gen.defineRect(pos.getMinX() / 1000f, pos.getMinY() / 1000f,
+                         pos.width / 1000f, pos.height / 1000f);
+                 gen.writeln("clip");
+             }
  
-         //Cloning SVG DOM as Batik attaches non-thread-safe facilities (like the CSS engine)
-         //to it.
-         Document clonedDoc = BatikUtil.cloneSVGDocument(imageSVG.getDocument());
+             // transform so that the coordinates (0,0) is from the top left
+             // and positive is down and to the right. (0,0) is where the
+             // viewBox puts it.
+             gen.concatMatrix(sx, 0, 0, sy, pos.getMinX() / 1000f, pos.getMinY() / 1000f);
  
-         GraphicsNode root;
+             AffineTransform transform = new AffineTransform();
+             // scale to viewbox
+             transform.translate(pos.getMinX(), pos.getMinY());
+             gen.getCurrentState().concatMatrix(transform);
+             try {
+                 root.paint(graphics);
+             } catch (Exception e) {
+                 SVGEventProducer eventProducer = SVGEventProducer.Provider.get(
+                         context.getUserAgent().getEventBroadcaster());
+                 eventProducer.svgRenderingError(this, e, image.getInfo().getOriginalURI());
+             }
+             gen.restoreGraphicsState();
+             gen.commentln("%FOPEndSVG");
+         }
+     }
+     private InputStream renderSVGToInputStream(RenderingContext context, ImageXMLDOM imageSVG) throws IOException {
+         PNGTranscoder png = new PNGTranscoder();
+         Float width = getDimension(imageSVG.getDocument(), "width") * 8;
+         png.addTranscodingHint(SVGAbstractTranscoder.KEY_WIDTH, width);
+         Float height = getDimension(imageSVG.getDocument(), "height") * 8;
+         png.addTranscodingHint(SVGAbstractTranscoder.KEY_HEIGHT, height);
+         TranscoderInput input = new TranscoderInput(imageSVG.getDocument());
+         ByteArrayOutputStream os = new ByteArrayOutputStream();
+         TranscoderOutput output = new TranscoderOutput(os);
          try {
-             GVTBuilder builder = new GVTBuilder();
-             root = builder.build(ctx, clonedDoc);
-         } catch (Exception e) {
+             png.transcode(input, output);
+         } catch (TranscoderException ex) {
              SVGEventProducer eventProducer = SVGEventProducer.Provider.get(
                      context.getUserAgent().getEventBroadcaster());
-             eventProducer.svgNotBuilt(this, e, image.getInfo().getOriginalURI());
-             return;
+             eventProducer.svgRenderingError(this, ex, imageSVG.getInfo().getOriginalURI());
+         } finally {
+             os.flush();
+             os.close();
+         }
+         return new ByteArrayInputStream(os.toByteArray());
+     }
+     private MaskedImage convertToRGB(BufferedImage alphaImage) {
+         int[] red = new int[256];
+         int[] green = new int[256];
+         int[] blue = new int[256];
+         BufferedImage rgbImage = new BufferedImage(alphaImage.getWidth(),
+                 alphaImage.getHeight(), BufferedImage.TYPE_INT_RGB);
+         //Count occurances of each colour in image
+         for (int cx = 0; cx < alphaImage.getWidth(); cx++) {
+             for (int cy = 0; cy < alphaImage.getHeight(); cy++) {
+                 int pixelValue = alphaImage.getRGB(cx, cy);
+                 Color pixelColor = new Color(pixelValue);
+                 red[pixelColor.getRed()]++;
+                 green[pixelColor.getGreen()]++;
+                 blue[pixelColor.getBlue()]++;
+             }
+         }
+         //Find colour not in image
+         Color alphaSwap = null;
+         for (int i = 0; i < 256; i++) {
+             if (red[i] == 0) {
+                 alphaSwap = new Color(i, 0, 0);
+                 break;
+             } else if (green[i] == 0) {
+                 alphaSwap = new Color(0, i, 0);
+                 break;
+             } else if (blue[i] == 0) {
+                 alphaSwap = new Color(0, 0, i);
+                 break;
+             }
          }
-         // get the 'width' and 'height' attributes of the SVG document
-         float w = (float)ctx.getDocumentSize().getWidth() * 1000f;
-         float h = (float)ctx.getDocumentSize().getHeight() * 1000f;
-         float sx = pos.width / w;
-         float sy = pos.height / h;
-         ctx = null;
-         gen.commentln("%FOPBeginSVG");
-         gen.saveGraphicsState();
-         final boolean clip = false;
-         if (clip) {
-             /*
-              * Clip to the svg area.
-              * Note: To have the svg overlay (under) a text area then use
-              * an fo:block-container
-              */
-             gen.writeln("newpath");
-             gen.defineRect(pos.getMinX() / 1000f, pos.getMinY() / 1000f,
-                     pos.width / 1000f, pos.height / 1000f);
-             gen.writeln("clip");
+         //Check if all variations are used in all three colours
+         if (alphaSwap == null) {
+             //Fallback colour is no unique colour channel can be found
+             alphaSwap = FALLBACK_COLOR;
          }
+         //Replace alpha channel with the new mask colour
+         for (int cx = 0; cx < alphaImage.getWidth(); cx++) {
+             for (int cy = 0; cy < alphaImage.getHeight(); cy++) {
+                 int pixelValue = alphaImage.getRGB(cx, cy);
+                 if (pixelValue == 0) {
+                     rgbImage.setRGB(cx, cy, alphaSwap.getRGB());
+                 } else {
+                     rgbImage.setRGB(cx, cy, alphaImage.getRGB(cx, cy));
+                 }
+             }
+         }
+         return new MaskedImage(rgbImage, alphaSwap);
+     }
+     private static class MaskedImage {
+         private Color maskColor = new Color(0, 0, 0);
+         private BufferedImage image;
+         public MaskedImage(BufferedImage image, Color maskColor) {
+             this.image = image;
+             this.maskColor = maskColor;
+         }
+         public Color getMaskColor() {
+             return maskColor;
+         }
+         public BufferedImage getImage() {
+             return image;
+         }
+     }
  
-         // transform so that the coordinates (0,0) is from the top left
-         // and positive is down and to the right. (0,0) is where the
-         // viewBox puts it.
-         gen.concatMatrix(sx, 0, 0, sy, pos.getMinX() / 1000f, pos.getMinY() / 1000f);
+     private Float getDimension(Document document, String dimension) {
+         if (document.getFirstChild().getAttributes().getNamedItem(dimension) != null) {
+             String width = document.getFirstChild().getAttributes().getNamedItem(dimension).getNodeValue();
+             width = width.replaceAll("[^\\d.]", "");
+             return Float.parseFloat(width);
+         }
+         return null;
+     }
  
-         AffineTransform transform = new AffineTransform();
-         // scale to viewbox
-         transform.translate(pos.getMinX(), pos.getMinY());
-         gen.getCurrentState().concatMatrix(transform);
+     private boolean shouldRaster(ImageXMLDOM image) {
+         //A list of objects on which to check opacity
          try {
-             root.paint(graphics);
-         } catch (Exception e) {
-             SVGEventProducer eventProducer = SVGEventProducer.Provider.get(
-                     context.getUserAgent().getEventBroadcaster());
-             eventProducer.svgRenderingError(this, e, image.getInfo().getOriginalURI());
+         List<String> gradMatches = new ArrayList<String>();
+         gradMatches.add("radialGradient");
+         gradMatches.add("linearGradient");
+         return recurseSVGElements(image.getDocument().getChildNodes(), gradMatches, false);
+         } finally {
+             gradientsFound.clear();
          }
+     }
  
-         gen.restoreGraphicsState();
-         gen.commentln("%FOPEndSVG");
+     private boolean recurseSVGElements(NodeList childNodes, List<String> gradMatches, boolean isMatched) {
+         boolean opacityFound = false;
+         for (int i = 0; i < childNodes.getLength(); i++) {
+             Node curNode = childNodes.item(i);
+             if (isMatched && curNode.getLocalName() != null && curNode.getLocalName().equals("stop")) {
+                 if (curNode.getAttributes().getNamedItem("style") != null) {
+                     String[] stylePairs = curNode.getAttributes().getNamedItem("style").getNodeValue()
+                             .split(";");
+                     for (int styleAtt = 0; styleAtt < stylePairs.length; styleAtt++) {
+                         String[] style = stylePairs[styleAtt].split(":");
+                         if (style[0].equalsIgnoreCase("stop-opacity")) {
+                             if (Double.parseDouble(style[1]) < 1) {
+                                 return true;
+                             }
+                         }
+                     }
+                 }
+                 if (curNode.getAttributes().getNamedItem("stop-opacity") != null) {
+                     String opacityValue = curNode.getAttributes().getNamedItem("stop-opacity").getNodeValue();
+                     if (Double.parseDouble(opacityValue) < 1) {
+                         return true;
+                     }
+                 }
+             }
+             String nodeName = curNode.getLocalName();
+             //Special case where rasterization needed for radial gradients
+             if (nodeName != null && nodeName.equals("ellipse")) {
+                 String found = "";
+                 String ellipseFill = curNode.getAttributes().getNamedItem("fill").getNodeValue();
+                 Pattern pattern = Pattern.compile("#(.*?)\\)");
+                 Matcher matcher = pattern.matcher(ellipseFill);
+                 if (matcher.find()) {
+                     found = matcher.group(1);
+                 }
+                 if (gradientsFound.get(found) != null) {
+                     return true;
+                 }
+             }
+             boolean inMatch = false;
+             if (!isMatched) {
+                 inMatch = nodeName != null && gradMatches.contains(nodeName);
+                 if (inMatch) {
+                     gradientsFound.put(curNode.getAttributes().getNamedItem("id").getNodeValue(), nodeName);
+                 }
+             } else {
+                 inMatch = true;
+             }
+             opacityFound = recurseSVGElements(curNode.getChildNodes(), gradMatches, inMatch);
+             if (opacityFound) {
+                 return true;
+             }
+         }
+         return opacityFound;
      }
  
      /** {@inheritDoc} */
index 42a18b17c2eb7efbecad52d8888db055493ff32a,132c633dc1f0c112ee2024e41ed0899c19928683..60a6020fc808393744cf081a2de50a6b54174333
@@@ -23,10 -23,12 +23,13 @@@ import java.awt.Dimension
  import java.awt.geom.AffineTransform;
  import java.awt.geom.Dimension2D;
  
+ import javax.xml.parsers.ParserConfigurationException;
  import javax.xml.parsers.SAXParserFactory;
  
+ import org.xml.sax.SAXException;
  import org.apache.batik.bridge.UserAgentAdapter;
 +import org.apache.batik.gvt.font.FontFamilyResolver;
  
  /**
   * A simple SVG user agent.
index f7a5825bd25fcf84731786d8e394649c69f2a861,0000000000000000000000000000000000000000..c9346588c0a62f6c8788361a4a37c72f31f6e0f2
mode 100644,000000..100644
--- /dev/null
@@@ -1,102 -1,0 +1,102 @@@
- import org.apache.fop.fonts.truetype.TTFFontLoader;
 +/*
 + * 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.svg.font;
 +
 +import java.io.File;
 +import java.io.IOException;
 +import java.net.URI;
 +import java.net.URISyntaxException;
 +
 +import org.apache.fop.apps.io.InternalResourceResolver;
 +import org.apache.fop.apps.io.ResourceResolverFactory;
 +import org.apache.fop.fonts.EmbeddingMode;
 +import org.apache.fop.fonts.EncodingMode;
 +import org.apache.fop.fonts.Font;
 +import org.apache.fop.fonts.FontInfo;
 +import org.apache.fop.fonts.FontMetrics;
-         TTFFontLoader fontLoader = new TTFFontLoader(new URI(filename), null, true,
++import org.apache.fop.fonts.truetype.OFFontLoader;
 +
 +class FontInfoBuilder {
 +
 +    public static final String DEJAVU_LGC_SERIF = "DejaVu LGC Serif";
 +
 +    public static final String DROID_SANS_MONO = "Droid Sans Mono";
 +
 +    private static final boolean USE_ADVANCED_BY_DEFAULT = true;
 +
 +    private FontInfo fontInfo;
 +
 +    private int fontKey;
 +
 +    public FontInfoBuilder() {
 +        reset();
 +    }
 +
 +    private void reset() {
 +        fontInfo = new FontInfo();
 +        fontKey = 1;
 +    }
 +
 +    public FontInfoBuilder useDejaVuLGCSerif() {
 +        return useDejaVuLGCSerif(USE_ADVANCED_BY_DEFAULT);
 +    }
 +
 +    public FontInfoBuilder useDejaVuLGCSerif(boolean useAdvanced) {
 +        try {
 +            return useFont(DEJAVU_LGC_SERIF, "DejaVuLGCSerif.ttf", useAdvanced);
 +        } catch (Exception e) {
 +            throw new RuntimeException(e);
 +        }
 +    }
 +
 +    public FontInfoBuilder useDroidSansMono() {
 +        return useDroidSansMono(USE_ADVANCED_BY_DEFAULT);
 +    }
 +
 +    public FontInfoBuilder useDroidSansMono(boolean useAdvanced) {
 +        try {
 +            return useFont(DROID_SANS_MONO, "DroidSansMono.ttf", useAdvanced);
 +        } catch (Exception e) {
 +            throw new RuntimeException(e);
 +        }
 +    }
 +
 +    private FontInfoBuilder useFont(String fontName, String filename, boolean useAdvanced)
 +            throws IOException, URISyntaxException {
 +        URI baseURI = new File("test/resources/fonts/ttf").toURI();
 +        InternalResourceResolver resolver = ResourceResolverFactory.createDefaultInternalResourceResolver(baseURI);
++        OFFontLoader fontLoader = new OFFontLoader(new URI(filename), null, true,
 +                EmbeddingMode.AUTO, EncodingMode.AUTO, true, useAdvanced, resolver);
 +        FontMetrics font = fontLoader.getFont();
 +        registerFont(font, "F" + fontKey++, fontName);
 +        return this;
 +    }
 +
 +    private void registerFont(FontMetrics font, String key, String familyName) {
 +        fontInfo.addMetrics(key, font);
 +        fontInfo.addFontProperties(key, familyName, Font.STYLE_NORMAL, Font.WEIGHT_NORMAL);
 +    }
 +
 +    public FontInfo build() {
 +        FontInfo fontInfo = this.fontInfo;
 +        reset();
 +        return fontInfo;
 +    }
 +}