]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
FOP-2504: Truetype support for AFP
authorSimon Steiner <ssteiner@apache.org>
Thu, 6 Aug 2015 10:59:51 +0000 (10:59 +0000)
committerSimon Steiner <ssteiner@apache.org>
Thu, 6 Aug 2015 10:59:51 +0000 (10:59 +0000)
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1694450 13f79535-47bb-0310-9956-ffa450edef68

16 files changed:
lib/xmlgraphics-commons-svn-trunk.jar
src/java/org/apache/fop/afp/AFPResourceManager.java
src/java/org/apache/fop/afp/fonts/CharacterSet.java
src/java/org/apache/fop/afp/fonts/CharacterSetBuilder.java
src/java/org/apache/fop/afp/fonts/FopCharacterSet.java
src/java/org/apache/fop/afp/modca/AbstractTripletStructuredObject.java
src/java/org/apache/fop/afp/modca/ActiveEnvironmentGroup.java
src/java/org/apache/fop/afp/modca/MapDataResource.java
src/java/org/apache/fop/afp/modca/ObjectContainer.java
src/java/org/apache/fop/afp/modca/Registry.java
src/java/org/apache/fop/afp/modca/triplets/FullyQualifiedNameTriplet.java
src/java/org/apache/fop/render/afp/AFPDocumentHandler.java
src/java/org/apache/fop/render/afp/AFPFontConfig.java
src/java/org/apache/fop/render/afp/AFPPainter.java
test/java/org/apache/fop/render/afp/AFPParser.java [new file with mode: 0644]
test/java/org/apache/fop/render/afp/AFPTrueTypeTestCase.java [new file with mode: 0644]

index 45f5937082e02c029d1fe611d328b70f0e8ab70f..551295eb2695b1b1bb7434ab4fb3124763699b9f 100644 (file)
Binary files a/lib/xmlgraphics-commons-svn-trunk.jar and b/lib/xmlgraphics-commons-svn-trunk.jar differ
index fc34925d27f085f7129b7466383e7e2b7405a4e4..d4df8daab243fb5ecd006324cda054a9645ee111 100644 (file)
@@ -36,15 +36,21 @@ import org.apache.fop.afp.fonts.AFPFont;
 import org.apache.fop.afp.fonts.CharacterSet;
 import org.apache.fop.afp.modca.AbstractNamedAFPObject;
 import org.apache.fop.afp.modca.AbstractPageObject;
+import org.apache.fop.afp.modca.ActiveEnvironmentGroup;
 import org.apache.fop.afp.modca.IncludeObject;
 import org.apache.fop.afp.modca.IncludedResourceObject;
+import org.apache.fop.afp.modca.ObjectContainer;
 import org.apache.fop.afp.modca.PageSegment;
 import org.apache.fop.afp.modca.Registry;
 import org.apache.fop.afp.modca.ResourceGroup;
 import org.apache.fop.afp.modca.ResourceObject;
+import org.apache.fop.afp.modca.triplets.EncodingTriplet;
+import org.apache.fop.afp.modca.triplets.FullyQualifiedNameTriplet;
 import org.apache.fop.afp.util.AFPResourceAccessor;
 import org.apache.fop.afp.util.AFPResourceUtil;
 import org.apache.fop.apps.io.InternalResourceResolver;
+import org.apache.fop.fonts.FontType;
+import org.apache.fop.render.afp.AFPFontConfig;
 
 /**
  * Manages the creation and storage of document resources
@@ -330,12 +336,19 @@ public class AFPResourceManager {
             //Embed fonts (char sets and code pages)
             if (charSet.getResourceAccessor() != null) {
                 AFPResourceAccessor accessor = charSet.getResourceAccessor();
-                createIncludedResource(
-                        charSet.getName(), accessor,
-                        ResourceObject.TYPE_FONT_CHARACTER_SET);
-                createIncludedResource(
-                        charSet.getCodePage(), accessor,
-                        ResourceObject.TYPE_CODE_PAGE);
+                if (afpFont.getFontType() == FontType.TRUETYPE) {
+
+                    createIncludedResource(afpFont.getFontName(), accessor.resolveURI("."), accessor,
+                            ResourceObject.TYPE_OBJECT_CONTAINER, true,
+                            ((AFPFontConfig.AFPTrueTypeFont) afpFont).getTTC());
+                } else {
+                    createIncludedResource(
+                            charSet.getName(), accessor,
+                            ResourceObject.TYPE_FONT_CHARACTER_SET);
+                    createIncludedResource(
+                            charSet.getCodePage(), accessor,
+                            ResourceObject.TYPE_CODE_PAGE);
+                }
             }
         }
     }
@@ -366,7 +379,7 @@ public class AFPResourceManager {
                     + " (" + e.getMessage() + ")");
         }
 
-        createIncludedResource(resourceName, uri, accessor, resourceObjectType);
+        createIncludedResource(resourceName, uri, accessor, resourceObjectType, false, null);
     }
 
     /**
@@ -378,7 +391,7 @@ public class AFPResourceManager {
      * @throws IOException if an I/O error occurs while loading the resource
      */
     public void createIncludedResource(String resourceName, URI uri, AFPResourceAccessor accessor,
-                byte resourceObjectType) throws IOException {
+                                       byte resourceObjectType, boolean truetype, String ttc) throws IOException {
         AFPResourceLevel resourceLevel = new AFPResourceLevel(ResourceType.PRINT_FILE);
 
         AFPResourceInfo resourceInfo = new AFPResourceInfo();
@@ -391,15 +404,46 @@ public class AFPResourceManager {
             if (log.isDebugEnabled()) {
                 log.debug("Adding included resource: " + resourceName);
             }
-            IncludedResourceObject resourceContent = new IncludedResourceObject(
-                        resourceName, accessor, uri);
-
-            ResourceObject resourceObject = factory.createResource(resourceName);
-            resourceObject.setDataObject(resourceContent);
-            resourceObject.setType(resourceObjectType);
 
             ResourceGroup resourceGroup = streamer.getResourceGroup(resourceLevel);
-            resourceGroup.addObject(resourceObject);
+
+            if (truetype) {
+                ResourceObject res = factory.createResource();
+                res.setType(ResourceObject.TYPE_OBJECT_CONTAINER);
+
+                ActiveEnvironmentGroup.setupTruetypeMDR(res, false);
+
+                ObjectContainer oc = factory.createObjectContainer();
+                InputStream is;
+                try {
+                    is = accessor.createInputStream(new URI("."));
+                } catch (URISyntaxException e) {
+                    throw new IOException(e);
+                }
+
+                if (ttc != null) {
+                    oc.setData(extractTTC(ttc, is));
+                } else {
+                    oc.setData(IOUtils.toByteArray(is));
+                }
+
+                ActiveEnvironmentGroup.setupTruetypeMDR(oc, true);
+
+                res.addTriplet(new EncodingTriplet(1200));
+
+                res.setFullyQualifiedName(FullyQualifiedNameTriplet.TYPE_REPLACE_FIRST_GID_NAME,
+                        FullyQualifiedNameTriplet.FORMAT_CHARSTR, resourceName, true);
+
+                res.setDataObject(oc);
+                resourceGroup.addObject(res);
+            } else {
+                ResourceObject resourceObject = factory.createResource(resourceName);
+                IncludedResourceObject resourceContent = new IncludedResourceObject(
+                        resourceName, accessor, uri);
+                resourceObject.setDataObject(resourceContent);
+                resourceObject.setType(resourceObjectType);
+                resourceGroup.addObject(resourceObject);
+            }
 
             //TODO what is the data object?
             cachedObject = new CachedObject(resourceName, null);
@@ -411,6 +455,23 @@ public class AFPResourceManager {
         }
     }
 
+    private byte[] extractTTC(String ttc, InputStream is) throws IOException {
+//        TrueTypeCollection trueTypeCollection = new TrueTypeCollection(is);
+//        for (TrueTypeFont ttf : trueTypeCollection.getFonts()) {
+//            String name = ttf.getNaming().getFontFamily();
+//            if (name.equals(ttc)) {
+//                ByteArrayOutputStream bos = new ByteArrayOutputStream();
+//                TTFSubsetter s = new TTFSubsetter(ttf, null);
+//                for (int i = 0; i < 256 * 256; i++) {
+//                    s.addCharCode(i);
+//                }
+//                s.writeToStream(bos);
+//                return bos.toByteArray();
+//            }
+//        }
+        throw new IOException(ttc + " not supported");
+    }
+
     /**
      * Creates an included resource extracting the named resource from an external source.
      * @param resourceName the name of the resource
index d57b14ed7247b2c9bd68787720f1613e594b3eda..593b405921cd6596e6498a9b53f867ced35bf8a7 100644 (file)
@@ -108,8 +108,12 @@ public class CharacterSet {
 
         // the character set name must be 8 chars long
         this.name = padName(name);
-        // the code page name must be 8 chars long
-        this.codePage = padName(codePage);
+        if (codePage == null) {
+            this.codePage = null;
+        } else {
+            // the code page name must be 8 chars long
+            this.codePage = padName(codePage);
+        }
 
         this.encoding = encoding;
         this.encoder = charsetType.getEncoder(encoding);
index e7b9041ba508cb8225a156c51f7e97eadc9dee30..39d2be8455d87fafea96cc5bafa14d55fa939f1e 100644 (file)
@@ -232,6 +232,12 @@ public abstract class CharacterSetBuilder {
                 eventProducer);
     }
 
+    public CharacterSet build(String characterSetName, String codePageName, String encoding,
+                              Typeface typeface, AFPResourceAccessor accessor, AFPEventProducer eventProducer)
+        throws IOException {
+        return new FopCharacterSet(codePageName, encoding, characterSetName, typeface, accessor, eventProducer);
+    }
+
     private CharacterSet processFont(String characterSetName, String codePageName, String encoding,
             CharacterSetType charsetType, AFPResourceAccessor accessor, AFPEventProducer eventProducer)
             throws IOException {
index d3af78f7bd215dc6633a628f23bdb5dd3e2c1736..9d53cc7eaa033bedde7b46f74da46c98ccf30163 100644 (file)
@@ -49,6 +49,12 @@ public class FopCharacterSet extends CharacterSet {
         this.charSet = charSet;
     }
 
+    public FopCharacterSet(String codePage, String encoding, String name, Typeface charSet,
+                           AFPResourceAccessor accessor, AFPEventProducer eventProducer) {
+        super(codePage, encoding, CharacterSetType.SINGLE_BYTE, name, accessor, eventProducer);
+        this.charSet = charSet;
+    }
+
     /**
      * Ascender height is the distance from the character baseline to the
      * top of the character box. A negative ascender height signifies that
index 662b344c172f6d104c17af1af2faf2e111c8ee7c..d840e20770104eb3e3255a39efba726a407ad552 100644 (file)
@@ -103,7 +103,7 @@ public abstract class AbstractTripletStructuredObject extends AbstractStructured
      *
      * @param triplet the triplet to add
      */
-    protected void addTriplet(AbstractTriplet triplet) {
+    public void addTriplet(AbstractTriplet triplet) {
         triplets.add(triplet);
     }
 
@@ -131,7 +131,11 @@ public abstract class AbstractTripletStructuredObject extends AbstractStructured
      * @param fqName the fully qualified name of this resource
      */
     public void setFullyQualifiedName(byte fqnType, byte fqnFormat, String fqName) {
-        addTriplet(new FullyQualifiedNameTriplet(fqnType, fqnFormat, fqName));
+        addTriplet(new FullyQualifiedNameTriplet(fqnType, fqnFormat, fqName, false));
+    }
+
+    public void setFullyQualifiedName(byte fqnType, byte fqnFormat, String fqName, boolean utf16be) {
+        addTriplet(new FullyQualifiedNameTriplet(fqnType, fqnFormat, fqName, utf16be));
     }
 
     /** @return the fully qualified name of this triplet or null if it does not exist */
index 05f48b47c4843852ea9ee96fb15235552a68a971..48eaa0a4083e6c6139b4b7b6eabc69bd9c2ddd18 100644 (file)
@@ -23,8 +23,17 @@ import java.io.IOException;
 import java.io.OutputStream;
 import java.util.List;
 
+import org.apache.fop.afp.AFPDataObjectInfo;
 import org.apache.fop.afp.Factory;
 import org.apache.fop.afp.fonts.AFPFont;
+import org.apache.fop.afp.modca.triplets.AbstractTriplet;
+import org.apache.fop.afp.modca.triplets.EncodingTriplet;
+import org.apache.fop.afp.modca.triplets.FullyQualifiedNameTriplet;
+import org.apache.fop.afp.modca.triplets.ObjectClassificationTriplet;
+import org.apache.fop.afp.util.BinaryUtils;
+import org.apache.fop.apps.MimeConstants;
+import org.apache.fop.fonts.FontType;
+import org.apache.fop.render.afp.AFPFontConfig;
 
 /**
  * An Active Environment Group (AEG) is associated with each page,
@@ -63,6 +72,8 @@ public final class ActiveEnvironmentGroup extends AbstractEnvironmentGroup {
     /** the resource manager */
     private final Factory factory;
 
+    private MapDataResource mdr;
+
     /**
      * Constructor for the ActiveEnvironmentGroup, this takes a
      * name parameter which must be 8 characters long.
@@ -169,27 +180,100 @@ public final class ActiveEnvironmentGroup extends AbstractEnvironmentGroup {
      * @param orientation the orientation of the font (e.g. 0, 90, 180, 270)
      */
     public void createFont(int fontRef, AFPFont font, int size, int orientation) {
-        MapCodedFont mapCodedFont = getCurrentMapCodedFont();
-        if (mapCodedFont == null) {
-            mapCodedFont = factory.createMapCodedFont();
-            mapCodedFonts.add(mapCodedFont);
-        }
+        if (font.getFontType() == FontType.TRUETYPE) {
+            if (mdr == null) {
+                mdr = factory.createMapDataResource();
+                mapCodedFonts.add(mdr);
+            }
+            mdr.addTriplet(new EncodingTriplet(1200));
+            String name = font.getFontName();
+            if (((AFPFontConfig.AFPTrueTypeFont)font).getTTC() != null) {
+                name = ((AFPFontConfig.AFPTrueTypeFont)font).getTTC();
+            }
+            mdr.setFullyQualifiedName(FullyQualifiedNameTriplet.TYPE_DATA_OBJECT_EXTERNAL_RESOURCE_REF,
+                    FullyQualifiedNameTriplet.FORMAT_CHARSTR, name, true);
+            mdr.addTriplet(new FontFullyQualifiedNameTriplet((byte) fontRef));
 
-        try {
-            mapCodedFont.addFont(fontRef, font, size, orientation);
-        } catch (MaximumSizeExceededException msee) {
-            mapCodedFont = factory.createMapCodedFont();
-            mapCodedFonts.add(mapCodedFont);
+            setupTruetypeMDR(mdr, false);
+            mdr.addTriplet(new DataObjectFontTriplet(size / 1000));
+            mdr.finishElement();
+        } else {
+            MapCodedFont mapCodedFont = getCurrentMapCodedFont();
+            if (mapCodedFont == null) {
+                mapCodedFont = factory.createMapCodedFont();
+                mapCodedFonts.add(mapCodedFont);
+            }
 
             try {
                 mapCodedFont.addFont(fontRef, font, size, orientation);
-            } catch (MaximumSizeExceededException ex) {
-                // Should never happen (but log just in case)
-                LOG.error("createFont():: resulted in a MaximumSizeExceededException");
+            } catch (MaximumSizeExceededException msee) {
+                mapCodedFont = factory.createMapCodedFont();
+                mapCodedFonts.add(mapCodedFont);
+
+                try {
+                    mapCodedFont.addFont(fontRef, font, size, orientation);
+                } catch (MaximumSizeExceededException ex) {
+                    // Should never happen (but log just in case)
+                    LOG.error("createFont():: resulted in a MaximumSizeExceededException");
+                }
             }
         }
     }
 
+    public static void setupTruetypeMDR(AbstractTripletStructuredObject mdr, boolean res) {
+        AFPDataObjectInfo dataInfo = new AFPDataObjectInfo();
+        dataInfo.setMimeType(MimeConstants.MIME_AFP_TRUETYPE);
+        mdr.setObjectClassification(ObjectClassificationTriplet.CLASS_DATA_OBJECT_FONT,
+                dataInfo.getObjectType(), res, false, res);
+    }
+
+    public static class FontFullyQualifiedNameTriplet extends AbstractTriplet {
+        private byte fqName;
+        public FontFullyQualifiedNameTriplet(byte fqName) {
+            super(FULLY_QUALIFIED_NAME);
+            this.fqName = fqName;
+        }
+
+        public int getDataLength() {
+            return 5;
+        }
+
+        public void writeToStream(OutputStream os) throws IOException {
+            byte[] data = getData();
+            data[2] = FullyQualifiedNameTriplet.TYPE_DATA_OBJECT_INTERNAL_RESOURCE_REF;
+            data[3] = FullyQualifiedNameTriplet.FORMAT_CHARSTR;
+            data[4] = fqName;
+            os.write(data);
+        }
+    }
+
+    static class DataObjectFontTriplet extends AbstractTriplet {
+        private int pointSize;
+
+        public DataObjectFontTriplet(int size) {
+            super(DATA_OBJECT_FONT_DESCRIPTOR);
+            pointSize = size;
+        }
+
+        public int getDataLength() {
+            return 16;
+        }
+
+        public void writeToStream(OutputStream os) throws IOException {
+            byte[] data = getData();
+            data[3] = 0x20;
+            byte[] pointSizeBytes = BinaryUtils.convert(pointSize * 20, 2);
+            data[4] = pointSizeBytes[0]; //vfs
+            data[5] = pointSizeBytes[1];
+//            data[6] = pointSizeBytes[0]; //hsf
+//            data[7] = pointSizeBytes[1];
+            //charrot
+            data[11] = 0x03; //encenv
+            data[13] = 0x01; //encid
+            os.write(data);
+        }
+    }
+
     /**
      * Getter method for the most recent MapCodedFont added to the
      * Active Environment Group (returns null if no MapCodedFonts exist)
index 0bac920bdfe75a4bf7d66550845cc74ef962fc53..c7aa758cfbb722a66cbf09d96a860f9486125e61 100644 (file)
@@ -21,7 +21,11 @@ package org.apache.fop.afp.modca;
 
 import java.io.IOException;
 import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
 
+import org.apache.fop.afp.modca.triplets.AbstractTriplet;
+import org.apache.fop.afp.modca.triplets.Triplet;
 import org.apache.fop.afp.util.BinaryUtils;
 
 /**
@@ -29,6 +33,7 @@ import org.apache.fop.afp.util.BinaryUtils;
  * required for presentation.
  */
 public class MapDataResource extends AbstractTripletStructuredObject {
+    private List<List<AbstractTriplet>> tripletsList = new ArrayList<List<AbstractTriplet>>();
 
     /**
      * Main constructor
@@ -36,23 +41,45 @@ public class MapDataResource extends AbstractTripletStructuredObject {
     public MapDataResource() {
     }
 
+    public void finishElement() {
+        tripletsList.add(triplets);
+        triplets = new ArrayList<AbstractTriplet>();
+    }
+
+    @Override
+    protected int getTripletDataLength() {
+        int dataLength = 0;
+        for (List<AbstractTriplet> l : tripletsList) {
+            dataLength += getTripletDataLength(l) + 2;
+        }
+        return dataLength;
+    }
+
+    private int getTripletDataLength(List<AbstractTriplet> l) {
+        int dataLength = 0;
+        for (Triplet triplet : l) {
+            dataLength += triplet.getDataLength();
+        }
+        return dataLength;
+    }
+
     /** {@inheritDoc} */
     public void writeToStream(OutputStream os) throws IOException {
         super.writeStart(os);
-        byte[] data = new byte[11];
+        byte[] data = new byte[9];
         copySF(data, Type.MAP, Category.DATA_RESOURCE);
 
         int tripletDataLen = getTripletDataLength();
 
-        byte[] len = BinaryUtils.convert(10 + tripletDataLen, 2);
+        byte[] len = BinaryUtils.convert(8 + tripletDataLen, 2);
         data[1] = len[0];
         data[2] = len[1];
-
-        len = BinaryUtils.convert(2 + tripletDataLen, 2);
-        data[9] = len[0];
-        data[10] = len[1];
-
         os.write(data);
-        writeTriplets(os);
+
+        for (List<AbstractTriplet> l : tripletsList) {
+            len = BinaryUtils.convert(2 + getTripletDataLength(l), 2);
+            os.write(len);
+            writeObjects(l, os);
+        }
     }
 }
index 50aabb03bf90550d104a86f67bc01a50d313d4d5..288e5aad0a65c70431dc4ad189712508bcb50a81 100644 (file)
@@ -36,7 +36,7 @@ import org.apache.fop.afp.util.BinaryUtils;
 public class ObjectContainer extends AbstractDataObject {
 
     /** the object container data maximum length */
-    private static final int MAX_DATA_LEN = 32759;
+    private static final int MAX_DATA_LEN = 8192;
 
     private byte[] data;
 
index 50aaca32c330931fc52cc55761bdc8cf7955173b..fe0a427901f455ff17bd78a0f9f657ddbd1bd503 100644 (file)
@@ -174,24 +174,24 @@ public final class Registry {
                         MimeConstants.MIME_PCL
                 )
         );
+        mimeObjectTypeMap.put(
+                MimeConstants.MIME_AFP_TRUETYPE,
+                new ObjectType(
+                        COMPID_TRUETYPE_OPENTYPE_FONT_RESOURCE_OBJECT,
+                        new byte[] {0x06, 0x07, 0x2B, 0x12, 0x00, 0x04, 0x01, 0x01, 0x33},
+                        "TrueType/OpenType Font Resource Object",
+                        true,
+                        MimeConstants.MIME_AFP_TRUETYPE
+                )
+        );
 //        mimeObjectTypeMap.put(
-//                null,
-//                new ObjectType(
-//                        COMPID_TRUETYPE_OPENTYPE_FONT_RESOURCE_OBJECT,
-//                        new byte[] {0x06, 0x07, 0x2B, 0x12, 0x00, 0x04, 0x01, 0x01, 0x33},
-//                        "TrueType/OpenType Font Resource Object",
-//                        true,
-//                        null
-//                )
-//        );
-//        mimeObjectTypeMap.put(
-//                null,
+//                MimeConstants.MIME_AFP_TTC,
 //                new ObjectType(
 //                        COMPID_TRUETYPE_OPENTYPE_FONT_COLLECTION_RESOURCE_OBJECT,
 //                        new byte[] {0x06, 0x07, 0x2B, 0x12, 0x00, 0x04, 0x01, 0x01, 0x35},
 //                        "TrueType/OpenType Font Collection Resource Object",
 //                        true,
-//                        null
+//                        MimeConstants.MIME_AFP_TTC
 //                )
 //        );
     }
index db6d8add88023dcda14b487d2ff6a8888ca4e5eb..581d215b86f53c4cbcdd5fbdaa1beafff225f77e 100644 (file)
@@ -150,6 +150,10 @@ public class FullyQualifiedNameTriplet extends AbstractTriplet {
     /** the actual fully qualified name */
     private final String fqName;
 
+    private String encoding = AFPConstants.EBCIDIC_ENCODING;
+
+    private int charlen = 1;
+
     /**
      * Main constructor
      *
@@ -157,11 +161,15 @@ public class FullyQualifiedNameTriplet extends AbstractTriplet {
      * @param format the fully qualified name format
      * @param fqName the fully qualified name
      */
-    public FullyQualifiedNameTriplet(byte type, byte format, String fqName) {
+    public FullyQualifiedNameTriplet(byte type, byte format, String fqName, boolean utf16be) {
         super(FULLY_QUALIFIED_NAME);
         this.type = type;
         this.format = format;
         this.fqName = fqName;
+        if (utf16be) {
+            encoding = "UTF-16BE";
+            charlen = 2;
+        }
     }
 
     /**
@@ -180,7 +188,7 @@ public class FullyQualifiedNameTriplet extends AbstractTriplet {
 
     /** {@inheritDoc} */
     public int getDataLength() {
-        return 4 + fqName.length();
+        return 4 + (fqName.length() * charlen);
     }
 
     /** {@inheritDoc} */
@@ -191,7 +199,6 @@ public class FullyQualifiedNameTriplet extends AbstractTriplet {
 
         // FQName
         byte[] fqNameBytes;
-        String encoding = AFPConstants.EBCIDIC_ENCODING;
         if (format == FORMAT_URL) {
             encoding = AFPConstants.US_ASCII_ENCODING;
         }
index 092e2a425ce9a55bfc936c90c4563b73d35ed65e..a2c1389d74cabbd7636c3454a237e8f2feede7eb 100644 (file)
@@ -399,7 +399,7 @@ public class AFPDocumentHandler extends AbstractBinaryWritingIFDocumentHandler
             try {
                 getResourceManager().createIncludedResource(formMap.getName(),
                         formMap.getSrc(), accessor,
-                        ResourceObject.TYPE_FORMDEF);
+                        ResourceObject.TYPE_FORMDEF, false, null);
             } catch (IOException ioe) {
                 throw new IFException(
                         "I/O error while embedding form map resource: " + formMap.getName(), ioe);
index 149973eddfd3674b0b298f7f94809d8d6a50f7a0..341123331065c03b1c77568fd66a33fb39ff3669 100644 (file)
@@ -20,6 +20,8 @@
 package org.apache.fop.render.afp;
 
 import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -42,12 +44,16 @@ import org.apache.fop.afp.util.AFPResourceAccessor;
 import org.apache.fop.apps.FOPException;
 import org.apache.fop.apps.io.InternalResourceResolver;
 import org.apache.fop.events.EventProducer;
+import org.apache.fop.fonts.EmbedFontInfo;
 import org.apache.fop.fonts.FontConfig;
 import org.apache.fop.fonts.FontManager;
 import org.apache.fop.fonts.FontManagerConfigurator;
 import org.apache.fop.fonts.FontTriplet;
 import org.apache.fop.fonts.FontTriplet.Matcher;
+import org.apache.fop.fonts.FontType;
+import org.apache.fop.fonts.FontUris;
 import org.apache.fop.fonts.FontUtil;
+import org.apache.fop.fonts.LazyFont;
 import org.apache.fop.fonts.Typeface;
 
 /**
@@ -154,6 +160,13 @@ public final class AFPFontConfig implements FontConfig {
                         triplet.getAttribute("style"), weight);
                 tripletList.add(fontTriplet);
             }
+
+            String tturi = fontCfg.getAttribute("embed-url", null);
+            if (tturi != null) {
+                fontFromType(tripletList, "truetype", null, "UTF-16BE", fontCfg, eventProducer, tturi);
+                return;
+            }
+
             //build the fonts
             Configuration[] config = fontCfg.getChildren("afp-font");
             if (config.length == 0) {
@@ -199,8 +212,9 @@ public final class AFPFontConfig implements FontConfig {
                         embedURI);
             } else if ("CIDKeyed".equalsIgnoreCase(type)) {
                 config = getCIDKeyedFont(fontTriplets, type, codepage, encoding, cfg,
-                        eventProducer,
-                        embedURI);
+                        eventProducer, embedURI);
+            } else if ("truetype".equalsIgnoreCase(type)) {
+                config = getTruetypeFont(fontTriplets, type, codepage, encoding, cfg, eventProducer, embedURI);
             } else {
                 LOG.error("No or incorrect type attribute: " + type);
             }
@@ -240,6 +254,19 @@ public final class AFPFontConfig implements FontConfig {
                     name, base14, isEmbbedable(fontTriplets), uri);
         }
 
+        private TrueTypeFontConfig getTruetypeFont(List<FontTriplet> fontTriplets, String type, String codepage,
+                                                   String encoding, Configuration cfg, AFPEventProducer eventProducer,
+                                                   String uri) throws ConfigurationException {
+            String name = cfg.getAttribute("name", null);
+            if (name == null) {
+                eventProducer.fontConfigMissing(this, "font name attribute", cfg.getLocation());
+                return null;
+            }
+            String subfont = cfg.getAttribute("sub-font", null);
+            return new TrueTypeFontConfig(fontTriplets, type, codepage, encoding, "",
+                    name, subfont, isEmbbedable(fontTriplets), uri);
+        }
+
         private RasterFontConfig getRasterFont(List<FontTriplet> triplets, String type,
                 String codepage, String encoding, Configuration cfg,
                 AFPEventProducer eventProducer, String uri)
@@ -281,12 +308,12 @@ public final class AFPFontConfig implements FontConfig {
     }
 
     abstract static class AFPFontConfigData {
-        private final List<FontTriplet> triplets;
+        protected final List<FontTriplet> triplets;
         private final String codePage;
         private final String encoding;
         private final String name;
         private final boolean embeddable;
-        private final String uri;
+        protected final String uri;
 
         AFPFontConfigData(List<FontTriplet> triplets, String type, String codePage,
                 String encoding, String name, boolean embeddable, String uri) {
@@ -334,6 +361,55 @@ public final class AFPFontConfig implements FontConfig {
         }
     }
 
+    static final class TrueTypeFontConfig extends AFPFontConfigData {
+        private String characterset;
+        private String subfont;
+
+        private TrueTypeFontConfig(List<FontTriplet> triplets, String type, String codePage,
+                                   String encoding, String characterset, String name, String subfont,
+                                   boolean embeddable, String uri) {
+            super(triplets, type, codePage, encoding, name, embeddable, uri);
+            this.characterset = characterset;
+            this.subfont = subfont;
+        }
+
+        @Override
+        AFPFontInfo getFontInfo(InternalResourceResolver resourceResolver, AFPEventProducer eventProducer)
+                throws IOException {
+            Typeface tf;
+            try {
+                tf = new LazyFont(new EmbedFontInfo(
+                        new FontUris(new URI(uri), null)
+                        , false, true, null, subfont), resourceResolver, false).getRealFont();
+            } catch (URISyntaxException e) {
+                throw new IOException(e);
+            }
+            AFPResourceAccessor accessor = getAccessor(resourceResolver);
+            CharacterSet characterSet = CharacterSetBuilder.getDoubleByteInstance().build(characterset, super.codePage,
+                    super.encoding, tf, accessor, eventProducer);
+            OutlineFont font = new AFPTrueTypeFont(super.name, super.embeddable, characterSet,
+                    eventProducer, subfont);
+            return getFontInfo(font, this);
+        }
+    }
+
+    public static class AFPTrueTypeFont extends OutlineFont {
+        private String ttc;
+        public AFPTrueTypeFont(String name, boolean embeddable, CharacterSet charSet, AFPEventProducer eventProducer,
+                               String ttc) {
+            super(name, embeddable, charSet, eventProducer);
+            this.ttc = ttc;
+        }
+
+        public FontType getFontType() {
+            return FontType.TRUETYPE;
+        }
+
+        public String getTTC() {
+            return ttc;
+        }
+    }
+
     static final class OutlineFontConfig extends AFPFontConfigData {
         private final String base14;
         private final String characterset;
index aaa702afa62358fbb0c0884e075a81585d537343..bfaa5db74e0fd4cac46322e607c7547b6f32b5a1 100644 (file)
@@ -68,6 +68,7 @@ import org.apache.fop.afp.ptoca.PtocaProducer;
 import org.apache.fop.afp.util.AFPResourceAccessor;
 import org.apache.fop.fonts.Font;
 import org.apache.fop.fonts.FontTriplet;
+import org.apache.fop.fonts.FontType;
 import org.apache.fop.fonts.Typeface;
 import org.apache.fop.render.ImageHandlerUtil;
 import org.apache.fop.render.RenderingContext;
@@ -973,11 +974,18 @@ public class AFPPainter extends AbstractIFPainter<AFPDocumentHandler> {
                         builder.setVariableSpaceCharacterIncrement(varSpaceCharacterIncrement);
 
                         boolean fixedSpaceMode = false;
+                        int ttPos = p.x;
 
                         for (int i = 0; i < l; i++) {
                             char orgChar = text.charAt(i);
                             float glyphAdjust = 0;
-                            if (CharUtilities.isFixedWidthSpace(orgChar)) {
+                            if (afpFont.getFontType() == FontType.TRUETYPE) {
+                                flushText(builder, sb, charSet);
+                                fixedSpaceMode = true;
+                                int charWidth = font.getCharWidth(orgChar);
+                                sb.append(orgChar);
+                                glyphAdjust += charWidth;
+                            } else if (CharUtilities.isFixedWidthSpace(orgChar)) {
                                 flushText(builder, sb, charSet);
                                 builder.setVariableSpaceCharacterIncrement(
                                         fixedSpaceCharacterIncrement);
@@ -1005,7 +1013,11 @@ public class AFPPainter extends AbstractIFPainter<AFPDocumentHandler> {
                                 glyphAdjust += dx[i + 1];
                             }
 
-                            if (glyphAdjust != 0) {
+                            if (afpFont.getFontType() == FontType.TRUETYPE) {
+                                flushText(builder, sb, charSet);
+                                ttPos += Math.round(unitConv.mpt2units(glyphAdjust));
+                                builder.absoluteMoveInline(ttPos);
+                            } else if (glyphAdjust != 0) {
                                 flushText(builder, sb, charSet);
                                 int increment = Math.round(unitConv.mpt2units(glyphAdjust));
                                 builder.relativeMoveInline(increment);
diff --git a/test/java/org/apache/fop/render/afp/AFPParser.java b/test/java/org/apache/fop/render/afp/AFPParser.java
new file mode 100644 (file)
index 0000000..9f0f923
--- /dev/null
@@ -0,0 +1,235 @@
+/*
+ * 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.render.afp;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.fop.afp.AFPConstants;
+import org.apache.fop.afp.ptoca.PtocaBuilder;
+
+import junit.framework.Assert;
+
+public class AFPParser {
+    private boolean readText;
+    public AFPParser(boolean readText) {
+        this.readText = readText;
+    }
+
+    public void read(InputStream bis, StringBuilder sb) throws IOException {
+        while (bis.available() > 0) {
+            readField(bis, sb);
+        }
+    }
+
+    private void readField(InputStream bis, StringBuilder sb) throws IOException {
+        bis.read();
+        int len = getLength(bis.read(), bis.read());
+        byte[] field = new byte[len - 2];
+        bis.read(field);
+        InputStream fieldStream = new ByteArrayInputStream(field);
+        fieldStream.read();
+        byte type = (byte) fieldStream.read();
+        byte category = (byte) fieldStream.read();
+        fieldStream.skip(3);
+        String typeStr = TYPE_MAP.get(type & 0xFF);
+        String catStr = CATEGORY_MAP.get(category & 0xFF);
+        if (typeStr != null && catStr != null) {
+            sb.append(typeStr + " " + catStr);
+            if (typeStr.equals("BEGIN") || typeStr.equals("END")) {
+                byte[] name = new byte[8];
+                fieldStream.read(name);
+                sb.append(" " + new String(name, AFPConstants.EBCIDIC_ENCODING));
+                fieldStream.skip(2);
+                readTriplet(fieldStream, sb);
+            } else if (typeStr.equals("MAP")) {
+                fieldStream.skip(2);
+                readTriplet(fieldStream, sb);
+            } else if (typeStr.equals("DESCRIPTOR") && catStr.equals("OBJECT_AREA")) {
+                readTriplet(fieldStream, sb);
+            } else if (typeStr.equals("DATA") && catStr.equals("PRESENTATION_TEXT") && readText) {
+                readData(fieldStream, sb);
+            }
+            sb.append("\n");
+        }
+    }
+
+    private void readData(InputStream bis, StringBuilder sb) throws IOException {
+        Assert.assertEquals(bis.read(), 0x2B);
+        Assert.assertEquals(bis.read(), 0xD3);
+        while (bis.available() > 0) {
+            int len = bis.read();
+            int functionType = bis.read();
+
+            sb.append(" " + PTOCA_MAP.get(functionType));
+
+            if ("TRN".equals(PTOCA_MAP.get(functionType))) {
+                byte[] data = new byte[len - 2];
+                bis.read(data);
+                sb.append(" " + new String(data, "UTF-16BE"));
+            } else {
+                bis.skip(len - 2);
+            }
+        }
+    }
+
+    private void readTriplet(InputStream des, StringBuilder sb) throws IOException {
+        if (des.available() > 0) {
+            sb.append(" Triplets: ");
+        }
+        while (des.available() > 0) {
+            int len2 = des.read();
+            int id = des.read();
+            int b = id & 0xFF;
+            if (TRIPLET_MAP.containsKey(b)) {
+                sb.append(TRIPLET_MAP.get(b) + ",");
+            } else {
+                sb.append(String.format("0x%02X,", b));
+            }
+            des.skip(len2 - 2);
+        }
+    }
+
+    private int getLength(int a, int b) {
+        return (a * 256) + b;
+    }
+
+    private static final Map<Integer, String> TYPE_MAP = new HashMap<Integer, String>();
+    private static final Map<Integer, String> CATEGORY_MAP = new HashMap<Integer, String>();
+    private static final Map<Integer, String> TRIPLET_MAP = new HashMap<Integer, String>();
+    private static final Map<Integer, String> PTOCA_MAP = new HashMap<Integer, String>();
+    static {
+        PTOCA_MAP.put(0xC2 | PtocaBuilder.CHAIN_BIT, "SIA");
+        PTOCA_MAP.put(0xC4 | PtocaBuilder.CHAIN_BIT, "SVI");
+        PTOCA_MAP.put(0xC6 | PtocaBuilder.CHAIN_BIT, "AMI");
+        PTOCA_MAP.put(0xC8 | PtocaBuilder.CHAIN_BIT, "RMI");
+        PTOCA_MAP.put(0xD2 | PtocaBuilder.CHAIN_BIT, "AMB");
+        PTOCA_MAP.put(0xDA | PtocaBuilder.CHAIN_BIT, "TRN");
+        PTOCA_MAP.put(0xE4 | PtocaBuilder.CHAIN_BIT, "DIR");
+        PTOCA_MAP.put(0xE6 | PtocaBuilder.CHAIN_BIT, "DBR");
+        PTOCA_MAP.put(0x80 | PtocaBuilder.CHAIN_BIT, "SEC");
+        PTOCA_MAP.put(0xF0 | PtocaBuilder.CHAIN_BIT, "SCFL");
+        PTOCA_MAP.put(0xF6 | PtocaBuilder.CHAIN_BIT, "STO");
+        PTOCA_MAP.put(0xF8 | PtocaBuilder.CHAIN_BIT, "NOP");
+
+        TYPE_MAP.put(0xA0, "ATTRIBUTE");
+        TYPE_MAP.put(0xA2, "COPY_COUNT");
+        TYPE_MAP.put(0xA6, "DESCRIPTOR");
+        TYPE_MAP.put(0xA7, "CONTROL");
+        TYPE_MAP.put(0xA8, "BEGIN");
+        TYPE_MAP.put(0xA9, "END");
+        TYPE_MAP.put(0xAB, "MAP");
+        TYPE_MAP.put(0xAC, "POSITION");
+        TYPE_MAP.put(0xAD, "PROCESS");
+        TYPE_MAP.put(0xAF, "INCLUDE");
+        TYPE_MAP.put(0xB0, "TABLE");
+        TYPE_MAP.put(0xB1, "MIGRATION");
+        TYPE_MAP.put(0xB2, "VARIABLE");
+        TYPE_MAP.put(0xB4, "LINK");
+        TYPE_MAP.put(0xEE, "DATA");
+
+        CATEGORY_MAP.put(0x5F, "PAGE_SEGMENT");
+        CATEGORY_MAP.put(0x6B, "OBJECT_AREA");
+        CATEGORY_MAP.put(0x77, "COLOR_ATTRIBUTE_TABLE");
+        CATEGORY_MAP.put(0x7B, "IM_IMAGE");
+        CATEGORY_MAP.put(0x88, "MEDIUM");
+        CATEGORY_MAP.put(0x8A, "CODED_FONT");
+        CATEGORY_MAP.put(0x90, "PROCESS_ELEMENT");
+        CATEGORY_MAP.put(0x92, "OBJECT_CONTAINER");
+        CATEGORY_MAP.put(0x9B, "PRESENTATION_TEXT");
+        CATEGORY_MAP.put(0xA7, "INDEX");
+        CATEGORY_MAP.put(0xA8, "DOCUMENT");
+        CATEGORY_MAP.put(0xAD, "PAGE_GROUP");
+        CATEGORY_MAP.put(0xAF, "PAGE");
+        CATEGORY_MAP.put(0xBB, "GRAPHICS");
+        CATEGORY_MAP.put(0xC3, "DATA_RESOURCE");
+        CATEGORY_MAP.put(0xC4, "DOCUMENT_ENVIRONMENT_GROUP");
+        CATEGORY_MAP.put(0xC6, "RESOURCE_GROUP");
+        CATEGORY_MAP.put(0xC7, "OBJECT_ENVIRONMENT_GROUP");
+        CATEGORY_MAP.put(0xC9, "ACTIVE_ENVIRONMENT_GROUP");
+        CATEGORY_MAP.put(0xCC, "MEDIUM_MAP");
+        CATEGORY_MAP.put(0xCD, "FORM_MAP");
+        CATEGORY_MAP.put(0xCE, "NAME_RESOURCE");
+        CATEGORY_MAP.put(0xD8, "PAGE_OVERLAY");
+        CATEGORY_MAP.put(0xD9, "RESOURCE_ENVIROMENT_GROUP");
+        CATEGORY_MAP.put(0xDF, "OVERLAY");
+        CATEGORY_MAP.put(0xEA, "DATA_SUPRESSION");
+        CATEGORY_MAP.put(0xEB, "BARCODE");
+        CATEGORY_MAP.put(0xEE, "NO_OPERATION");
+        CATEGORY_MAP.put(0xFB, "IMAGE");
+
+        TRIPLET_MAP.put(0x02, "FULLY_QUALIFIED_NAME");
+        TRIPLET_MAP.put(0x04, "MAPPING_OPTION");
+        TRIPLET_MAP.put(0x10, "OBJECT_CLASSIFICATION");
+        TRIPLET_MAP.put(0x18, "MODCA_INTERCHANGE_SET");
+        TRIPLET_MAP.put(0x1F, "FONT_DESCRIPTOR_SPECIFICATION");
+        TRIPLET_MAP.put(0x21, "OBJECT_FUNCTION_SET_SPECIFICATION");
+        TRIPLET_MAP.put(0x22, "EXTENDED_RESOURCE_LOCAL_IDENTIFIER");
+        TRIPLET_MAP.put(0x24, "RESOURCE_LOCAL_IDENTIFIER");
+        TRIPLET_MAP.put(0x25, "RESOURCE_SECTION_NUMBER");
+        TRIPLET_MAP.put(0x26, "CHARACTER_ROTATION");
+        TRIPLET_MAP.put(0x2D, "OBJECT_BYTE_OFFSET");
+        TRIPLET_MAP.put(0x36, "ATTRIBUTE_VALUE");
+        TRIPLET_MAP.put(0x43, "DESCRIPTOR_POSITION");
+        TRIPLET_MAP.put(0x45, "MEDIA_EJECT_CONTROL");
+        TRIPLET_MAP.put(0x46, "PAGE_OVERLAY_CONDITIONAL_PROCESSING");
+        TRIPLET_MAP.put(0x47, "RESOURCE_USAGE_ATTRIBUTE");
+        TRIPLET_MAP.put(0x4B, "MEASUREMENT_UNITS");
+        TRIPLET_MAP.put(0x4C, "OBJECT_AREA_SIZE");
+        TRIPLET_MAP.put(0x4D, "AREA_DEFINITION");
+        TRIPLET_MAP.put(0x4E, "COLOR_SPECIFICATION");
+        TRIPLET_MAP.put(0x50, "ENCODING_SCHEME_ID");
+        TRIPLET_MAP.put(0x56, "MEDIUM_MAP_PAGE_NUMBER");
+        TRIPLET_MAP.put(0x57, "OBJECT_BYTE_EXTENT");
+        TRIPLET_MAP.put(0x58, "OBJECT_STRUCTURED_FIELD_OFFSET");
+        TRIPLET_MAP.put(0x59, "OBJECT_STRUCTURED_FIELD_EXTENT");
+        TRIPLET_MAP.put(0x5A, "OBJECT_OFFSET");
+        TRIPLET_MAP.put(0x5D, "FONT_HORIZONTAL_SCALE_FACTOR");
+        TRIPLET_MAP.put(0x5E, "OBJECT_COUNT");
+        TRIPLET_MAP.put(0x62, "OBJECT_DATE_AND_TIMESTAMP");
+        TRIPLET_MAP.put(0x65, "COMMENT");
+        TRIPLET_MAP.put(0x68, "MEDIUM_ORIENTATION");
+        TRIPLET_MAP.put(0x6C, "RESOURCE_OBJECT_INCLUDE");
+        TRIPLET_MAP.put(0x70, "PRESENTATION_SPACE_RESET_MIXING");
+        TRIPLET_MAP.put(0x71, "PRESENTATION_SPACE_MIXING_RULE");
+        TRIPLET_MAP.put(0x72, "UNIVERSAL_DATE_AND_TIMESTAMP");
+        TRIPLET_MAP.put(0x74, "TONER_SAVER");
+        TRIPLET_MAP.put(0x75, "COLOR_FIDELITY");
+        TRIPLET_MAP.put(0x78, "FONT_FIDELITY");
+        TRIPLET_MAP.put(0x80, "ATTRIBUTE_QUALIFIER");
+        TRIPLET_MAP.put(0x81, "PAGE_POSITION_INFORMATION");
+        TRIPLET_MAP.put(0x82, "PARAMETER_VALUE");
+        TRIPLET_MAP.put(0x83, "PRESENTATION_CONTROL");
+        TRIPLET_MAP.put(0x84, "FONT_RESOLUTION_AND_METRIC_TECHNOLOGY");
+        TRIPLET_MAP.put(0x85, "FINISHING_OPERATION");
+        TRIPLET_MAP.put(0x86, "TEXT_FIDELITY");
+        TRIPLET_MAP.put(0x87, "MEDIA_FIDELITY");
+        TRIPLET_MAP.put(0x88, "FINISHING_FIDELITY");
+        TRIPLET_MAP.put(0x8B, "DATA_OBJECT_FONT_DESCRIPTOR");
+        TRIPLET_MAP.put(0x8C, "LOCALE_SELECTOR");
+        TRIPLET_MAP.put(0x8E, "UP3I_FINISHING_OPERATION");
+        TRIPLET_MAP.put(0x91, "COLOR_MANAGEMENT_RESOURCE_DESCRIPTOR");
+        TRIPLET_MAP.put(0x95, "RENDERING_INTENT");
+        TRIPLET_MAP.put(0x96, "CMR_TAG_FIDELITY");
+        TRIPLET_MAP.put(0x97, "DEVICE_APPEARANCE");
+    }
+}
diff --git a/test/java/org/apache/fop/render/afp/AFPTrueTypeTestCase.java b/test/java/org/apache/fop/render/afp/AFPTrueTypeTestCase.java
new file mode 100644 (file)
index 0000000..6e6bbfe
--- /dev/null
@@ -0,0 +1,181 @@
+/*
+ * 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.render.afp;
+
+import java.awt.Color;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.sax.SAXResult;
+import javax.xml.transform.stream.StreamSource;
+
+import org.junit.Test;
+
+import org.xml.sax.SAXException;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.apache.fop.afp.AFPPaintingState;
+import org.apache.fop.afp.AFPResourceManager;
+import org.apache.fop.afp.DataStream;
+import org.apache.fop.afp.Factory;
+import org.apache.fop.afp.fonts.FopCharacterSet;
+import org.apache.fop.afp.modca.PageObject;
+import org.apache.fop.apps.FOUserAgent;
+import org.apache.fop.apps.Fop;
+import org.apache.fop.apps.FopConfParser;
+import org.apache.fop.apps.FopFactory;
+import org.apache.fop.apps.FopFactoryBuilder;
+import org.apache.fop.fonts.EmbeddingMode;
+import org.apache.fop.fonts.FontInfo;
+import org.apache.fop.fonts.FontTriplet;
+import org.apache.fop.fonts.MultiByteFont;
+import org.apache.fop.render.intermediate.IFException;
+
+import junit.framework.Assert;
+
+public class AFPTrueTypeTestCase {
+    @Test
+    public void testAFPTrueType() throws IOException, SAXException, TransformerException {
+        String fopxconf = "<fop version=\"1.0\">\n"
+                + "  <renderers>\n"
+                + "    <renderer mime=\"application/x-afp\">\n"
+                + "      <fonts>\n"
+                + "        <font name=\"Univers\" embed-url=\"test/resources/fonts/ttf/DejaVuLGCSerif.ttf\">\n"
+                + "          <font-triplet name=\"Univers\" style=\"normal\" weight=\"normal\"/>\n"
+                + "          <font-triplet name=\"any\" style=\"normal\" weight=\"normal\"/>\n"
+                + "        </font>\n"
+                + "      </fonts>\n"
+                + "    </renderer>\n"
+                + "  </renderers>\n"
+                + "</fop>";
+        String fo = "<fo:root xmlns:fo=\"http://www.w3.org/1999/XSL/Format\">\n"
+                + "  <fo:layout-master-set>\n"
+                + "    <fo:simple-page-master master-name=\"simple\">\n"
+                + "      <fo:region-body />\n"
+                + "    </fo:simple-page-master>\n"
+                + "  </fo:layout-master-set>\n"
+                + "  <fo:page-sequence master-reference=\"simple\">\n"
+                + "    <fo:flow flow-name=\"xsl-region-body\">\n"
+                + "      <fo:block font-family=\"Univers\">Univers</fo:block>\n"
+                + "    </fo:flow>\n"
+                + "  </fo:page-sequence>\n"
+                + "</fo:root>";
+
+        FopFactoryBuilder confBuilder = new FopConfParser(
+                new ByteArrayInputStream(fopxconf.getBytes()), new File(".").toURI()).getFopFactoryBuilder();
+        FopFactory fopFactory = confBuilder.build();
+        FOUserAgent foUserAgent = fopFactory.newFOUserAgent();
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        Fop fop = fopFactory.newFop("application/x-afp", foUserAgent, bos);
+        TransformerFactory factory = TransformerFactory.newInstance();
+        Transformer transformer = factory.newTransformer();
+        Source src = new StreamSource(new ByteArrayInputStream(fo.getBytes()));
+        Result res = new SAXResult(fop.getDefaultHandler());
+        transformer.transform(src, res);
+        bos.close();
+
+        StringBuilder sb = new StringBuilder();
+        InputStream bis = new ByteArrayInputStream(bos.toByteArray());
+        new AFPParser(false).read(bis, sb);
+
+        String format = "BEGIN RESOURCE_GROUP RG000001\n"
+                + "BEGIN NAME_RESOURCE RES00001 Triplets: OBJECT_FUNCTION_SET_SPECIFICATION"
+                + ",OBJECT_CLASSIFICATION,0x01,FULLY_QUALIFIED_NAME,\n"
+                + "BEGIN OBJECT_CONTAINER OC000001 Triplets: 0x41,0x00,0x00,\n";
+        for (int i = 0; i < 29; i++) {
+            format += "DATA OBJECT_CONTAINER\n";
+        }
+        format += "END OBJECT_CONTAINER OC000001\n"
+                + "END NAME_RESOURCE RES00001\n"
+                + "END RESOURCE_GROUP RG000001\n"
+                + "BEGIN DOCUMENT DOC00001\n"
+                + "BEGIN PAGE_GROUP PGP00001\n"
+                + "BEGIN PAGE PGN00001\n"
+                + "BEGIN ACTIVE_ENVIRONMENT_GROUP AEG00001\n"
+                + "MAP DATA_RESOURCE Triplets: 0x01,FULLY_QUALIFIED_NAME,FULLY_QUALIFIED_NAME"
+                + ",OBJECT_CLASSIFICATION,DATA_OBJECT_FONT_DESCRIPTOR,\n"
+                + "DESCRIPTOR PAGE\n"
+                + "MIGRATION PRESENTATION_TEXT\n"
+                + "END ACTIVE_ENVIRONMENT_GROUP AEG00001\n"
+                + "BEGIN PRESENTATION_TEXT PT000001\n"
+                + "DATA PRESENTATION_TEXT\n"
+                + "END PRESENTATION_TEXT PT000001\n"
+                + "END PAGE PGN00001\n"
+                + "END PAGE_GROUP PGP00001\n"
+                + "END DOCUMENT DOC00001\n";
+
+        Assert.assertEquals(sb.toString(), format);
+    }
+
+    @Test
+    public void testAFPPainter() throws IFException, IOException {
+        AFPDocumentHandler afpDocumentHandler = mock(AFPDocumentHandler.class);
+        when(afpDocumentHandler.getPaintingState()).thenReturn(new AFPPaintingState());
+        when(afpDocumentHandler.getResourceManager()).thenReturn(new AFPResourceManager(null));
+
+        DataStream ds = mock(DataStream.class);
+        when(afpDocumentHandler.getDataStream()).thenReturn(ds);
+        PageObject po = new PageObject(new Factory(), "PAGE0001", 0, 0, 0, 0, 0);
+        when(ds.getCurrentPage()).thenReturn(po);
+
+        AFPPainter afpPainter = new MyAFPPainter(afpDocumentHandler);
+        afpPainter.setFont("any", "normal", 400, null, null, Color.BLACK);
+        afpPainter.drawText(0, 0, 0, 0, null, "test");
+
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        po.writeToStream(bos);
+
+        InputStream bis = new ByteArrayInputStream(bos.toByteArray());
+        StringBuilder sb = new StringBuilder();
+        new AFPParser(true).read(bis, sb);
+        Assert.assertTrue(sb.toString(),
+                sb.toString().contains("DATA PRESENTATION_TEXT AMB AMI SCFL TRN t TRN e TRN s TRN t"));
+    }
+
+    class MyAFPPainter extends AFPPainter {
+        public MyAFPPainter(AFPDocumentHandler documentHandler) {
+            super(documentHandler);
+        }
+
+        protected FOUserAgent getUserAgent() {
+            FopFactory fopFactory = FopFactory.newInstance(new File(".").toURI());
+            return fopFactory.newFOUserAgent();
+        }
+
+        protected FontInfo getFontInfo() {
+            FontInfo f = new FontInfo();
+            f.addFontProperties("any", FontTriplet.DEFAULT_FONT_TRIPLET);
+            MultiByteFont font = new MultiByteFont(null, EmbeddingMode.AUTO);
+            font.setWidthArray(new int[100]);
+            f.addMetrics("any", new AFPFontConfig.AFPTrueTypeFont("", true,
+                    new FopCharacterSet("", "UTF-16BE", "", font, null, null), null, null));
+            return f;
+        }
+    }
+}