]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
Creation of soft fonts for TrueType fonts in PCL
authorRobert Meyer <rmeyer@apache.org>
Fri, 12 Jun 2015 15:52:55 +0000 (15:52 +0000)
committerRobert Meyer <rmeyer@apache.org>
Fri, 12 Jun 2015 15:52:55 +0000 (15:52 +0000)
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/Temp_PCLSoftFonts@1685112 13f79535-47bb-0310-9956-ffa450edef68

35 files changed:
findbugs-exclude.xml
src/java/org/apache/fop/fonts/truetype/GlyfTable.java
src/java/org/apache/fop/fonts/truetype/OFDirTabEntry.java
src/java/org/apache/fop/fonts/truetype/OpenFont.java
src/java/org/apache/fop/fonts/truetype/TTFFile.java
src/java/org/apache/fop/render/java2d/ConfiguredFontCollection.java
src/java/org/apache/fop/render/java2d/CustomFontMetricsMapper.java
src/java/org/apache/fop/render/pcl/HardcodedFonts.java
src/java/org/apache/fop/render/pcl/PCLEventProducer.java
src/java/org/apache/fop/render/pcl/PCLEventProducer.xml
src/java/org/apache/fop/render/pcl/PCLGenerator.java
src/java/org/apache/fop/render/pcl/PCLPainter.java
src/java/org/apache/fop/render/pcl/PCLRendererConfigurator.java
src/java/org/apache/fop/render/pcl/PCLRenderingUtil.java
src/java/org/apache/fop/render/pcl/fonts/PCLByteWriterUtil.java [new file with mode: 0644]
src/java/org/apache/fop/render/pcl/fonts/PCLCharacterDefinition.java [new file with mode: 0644]
src/java/org/apache/fop/render/pcl/fonts/PCLCharacterWriter.java [new file with mode: 0644]
src/java/org/apache/fop/render/pcl/fonts/PCLFontReader.java [new file with mode: 0644]
src/java/org/apache/fop/render/pcl/fonts/PCLFontReaderFactory.java [new file with mode: 0644]
src/java/org/apache/fop/render/pcl/fonts/PCLFontSegment.java [new file with mode: 0644]
src/java/org/apache/fop/render/pcl/fonts/PCLSoftFont.java [new file with mode: 0644]
src/java/org/apache/fop/render/pcl/fonts/PCLSoftFontManager.java [new file with mode: 0644]
src/java/org/apache/fop/render/pcl/fonts/PCLSymbolSet.java [new file with mode: 0644]
src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFCharacterWriter.java [new file with mode: 0644]
src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFFontReader.java [new file with mode: 0644]
src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFOS2FontTable.java [new file with mode: 0644]
src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFPCLTFontTable.java [new file with mode: 0644]
src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFPOSTFontTable.java [new file with mode: 0644]
src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFTable.java [new file with mode: 0644]
src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFTableFactory.java [new file with mode: 0644]
test/java/org/apache/fop/render/pcl/fonts/MockPCLTTFFontReader.java [new file with mode: 0644]
test/java/org/apache/fop/render/pcl/fonts/PCLByteWriterUtilTestCase.java [new file with mode: 0644]
test/java/org/apache/fop/render/pcl/fonts/PCLFontReaderFactoryTestCase.java [new file with mode: 0644]
test/java/org/apache/fop/render/pcl/fonts/PCLTTFFontReaderTestCase.java [new file with mode: 0644]
test/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFCharacterWriterTestCase.java [new file with mode: 0644]

index 2eaa8580b370e1bd67c4562c92880bb79eb2f153..13f811251028b4ff8c86cad6e93b3d538b0f2f07 100644 (file)
        <Bug pattern="OS_OPEN_STREAM_EXCEPTION_PATH"/>
      </Or>
    </Match> 
+  <Match>
+    <Class name="org.apache.fop.render.pcl.fonts.truetype.PCLTTFTable"/>
+    <Bug pattern="ICAST_INTEGER_MULTIPLY_CAST_TO_LONG"/>
+  </Match>
   <!-- END - APPROVED EXCLUSIONS -->
 
   <!-- START - TEMPORARY (UNAPPROVED) EXCLUSIONS -->
index 1233a586e24c7648e36443e7fad9af3e2a0d3b1e..6ad479a0e9597aa63faf38cbbd0d98f5564a7968 100644 (file)
@@ -47,7 +47,7 @@ public class GlyfTable {
     /** All the glyphs that are composed, but do not appear in the subset. */
     protected Set<Integer> composedGlyphs = new TreeSet<Integer>();
 
-    protected GlyfTable(FontFileReader in, OFMtxEntry[] metrics, OFDirTabEntry dirTableEntry,
+    public GlyfTable(FontFileReader in, OFMtxEntry[] metrics, OFDirTabEntry dirTableEntry,
                         Map<Integer, Integer> glyphs) throws IOException {
         mtxTab = metrics;
         tableOffset = dirTableEntry.getOffset();
@@ -202,7 +202,7 @@ public class GlyfTable {
         } while (GlyfFlags.hasMoreComposites(flags));
     }
 
-    private boolean isComposite(int indexInOriginal) throws IOException {
+    public boolean isComposite(int indexInOriginal) throws IOException {
         int numberOfContours = in.readTTFShort(tableOffset + mtxTab[indexInOriginal].getOffset());
         return numberOfContours < 0;
     }
@@ -215,7 +215,7 @@ public class GlyfTable {
      * @return the set of glyph indices this glyph composes
      * @throws IOException an I/O error
      */
-    private Set<Integer> retrieveComposedGlyphs(int indexInOriginal)
+    public Set<Integer> retrieveComposedGlyphs(int indexInOriginal)
             throws IOException {
         Set<Integer> composedGlyphs = new HashSet<Integer>();
         long offset = tableOffset + mtxTab[indexInOriginal].getOffset() + 10;
index 0e468eb7b3fe05f707323f98886c4dddab6deba9..34e7fba14b10f31de8c04fea98bc0ab8c9d2ddf5 100644 (file)
@@ -30,7 +30,7 @@ import java.util.Arrays;
 public class OFDirTabEntry {
 
     private byte[] tag = new byte[4];
-    private int checksum;
+    private long checksum;
     private long offset;
     private long length;
 
@@ -74,7 +74,7 @@ public class OFDirTabEntry {
      * Returns the checksum.
      * @return int
      */
-    public int getChecksum() {
+    public long getChecksum() {
         return checksum;
     }
 
index 9d3a4769d7de54925b3ddea23976b319dff49ded..62a016c8ab2d55e7131392397ac001e25f012973 100644 (file)
@@ -355,7 +355,7 @@ public abstract class OpenFont {
                   long offset) throws IOException {
         OFDirTabEntry dt = dirTabs.get(tableName);
         if (dt == null) {
-            log.error("Dirtab " + tableName.getName() + " not found.");
+            log.info("Dirtab " + tableName.getName() + " not found.");
             return false;
         } else {
             in.seekSet(dt.getOffset() + offset);
@@ -965,6 +965,15 @@ public abstract class OpenFont {
         return fbb;
     }
 
+    /**
+     * Returns the original bounding box values from the HEAD table
+     * @return An array of bounding box values
+     */
+    public int[] getBBoxRaw() {
+        int[] bbox = {fontBBox1, fontBBox2, fontBBox3, fontBBox4};
+        return bbox;
+    }
+
     /**
      * Returns the LowerCaseAscent attribute of the font.
      * @return int The LowerCaseAscent
@@ -1047,6 +1056,18 @@ public abstract class OpenFont {
         return convertTTFUnit2PDFUnit(ansiWidth[idx]);
     }
 
+    /**
+     * Returns the width of a given character in raw units
+     * @param idx Index of the character
+     * @return int Width in it's raw form stored in the font
+     */
+    public int getCharWidthRaw(int idx) {
+        if (ansiWidth != null) {
+            return ansiWidth[idx];
+        }
+        return -1;
+    }
+
     /**
      * Returns the kerning table.
      * @return Map The kerning table
@@ -1979,4 +2000,8 @@ public abstract class OpenFont {
             IOUtils.closeQuietly(stream);
         }
     }
+
+    public String getCopyrightNotice() {
+        return notice;
+    }
 }
index 52df45ffbdf3e44228a287639295106eaf8b81b6..4b0e3d6287ed1a880c708e028e2357b21eabb780 100644 (file)
@@ -188,6 +188,14 @@ public class TTFFile extends OpenFont {
                     : (fontFile.readTTFUShort() << 1));
     }
 
+    /**
+     * Gets the last location of the glyf table
+     * @return The last location as a long
+     */
+    public long getLastGlyfLocation() {
+        return lastLoca;
+    }
+
     @Override
     protected void initializeFont(FontFileReader in) throws IOException {
         fontFile = in;
index b9b7539fc830adce6fdd16c1cb9e372a67d856a2..c6f02506e6a01f282321d6bc32decfa694f1984e 100644 (file)
@@ -83,7 +83,8 @@ public class ConfiguredFontCollection implements FontCollection {
                     font = new CustomFontMetricsMapper(fontMetrics, fontSource);
                 } else {
                     FontUris fontUris = new FontUris(fontURI, null);
-                    CustomFont fontMetrics = FontLoader.loadFont(fontUris, null, true,
+                    CustomFont fontMetrics = FontLoader.loadFont(fontUris,
+                            configFontInfo.getSubFontName(), true,
                             configFontInfo.getEmbeddingMode(), configFontInfo.getEncodingMode(),
                             configFontInfo.getKerning(), configFontInfo.getAdvanced(), resourceResolver);
                     font = new CustomFontMetricsMapper(fontMetrics);
index 62283915f1c214c8d94f031ecffbde95c7783066..c7f5da116e4d62c04da5cfbd501d829c4860d2f3 100644 (file)
@@ -289,4 +289,8 @@ public class CustomFontMetricsMapper extends Typeface implements FontMetricsMapp
         }
     }
 
+    public Typeface getRealFont() {
+        return typeface;
+    }
+
 }
index 185e1ece5491d8f9b9384769169b99b84a419a38..82e6220981f79a50da44611f87edc5b54b975b78 100644 (file)
@@ -55,7 +55,7 @@ final class HardcodedFonts {
         return selectFont(gen, name, size);
     }
 
-    private static boolean selectFont(PCLGenerator gen, String name, int size) throws IOException {
+    protected static boolean selectFont(PCLGenerator gen, String name, int size) throws IOException {
         int fontcode = 0;
         if (name.length() > 1 && name.charAt(0) == 'F') {
             try {
index 7d2ed252ec56b0689966df67b1960c2cde874acd..3a8a1de555a03474020375db387fc8a67be5f7f4 100644 (file)
@@ -53,4 +53,13 @@ public interface PCLEventProducer extends EventProducer {
      */
     void paperTypeUnavailable(Object source, long pageWidth, long pageHeight, String fallbackPaper);
 
+    /**
+     * The font type is not supported for PCL output.
+     * @param source The event source
+     * @param fontName The name of the font not supported
+     * @param supportedTypes The types of fonts currently supported
+     * @event.severity ERROR
+     */
+    void fontTypeNotSupported(Object source, String fontName, String supportedTypes);
+
 }
index 5d5785a4c6015a4678f384fc1029d34ebb42c504..463fcb16affc222efe37708677f200aabea65c66 100644 (file)
@@ -1,4 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <catalogue xml:lang="en">
   <message key="paperTypeUnavailable">Paper type ({pageWidth} x {pageHeight} mpt) could not be determined. Falling back to: {fallbackPaper}</message>
+  <message key="fontTypeNotSupported">The font '{fontName}' is not supported. PCL output currently only supports {supportedTypes}</message>
 </catalogue>
index 05a0fc66632294f45cd9205c3e094afe7ca49d95..67d37fbb59d5cdec84f00302d4798a17adbc6201 100644 (file)
@@ -140,6 +140,15 @@ public class PCLGenerator {
         out.write(s.getBytes(ISO_8859_1));
     }
 
+    /**
+     * Writes raw bytes to the output stream
+     * @param bytes The bytes
+     * @throws IOException In case of an I/O error
+     */
+    public void writeBytes(byte[] bytes) throws IOException {
+        out.write(bytes);
+    }
+
     /**
      * Formats a double value with two decimal positions for PCL output.
      *
index 9f7de49304d6dc4de3f4b482a46e222251b36cda..26a6da66b5fe4c8a8df08a60c8557fd42ce18708 100644 (file)
@@ -28,6 +28,7 @@ import java.awt.Rectangle;
 import java.awt.geom.AffineTransform;
 import java.awt.geom.Point2D;
 import java.awt.geom.Rectangle2D;
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.util.Map;
 import java.util.Stack;
@@ -42,16 +43,26 @@ import org.apache.xmlgraphics.image.loader.impl.ImageGraphics2D;
 import org.apache.xmlgraphics.java2d.GraphicContext;
 import org.apache.xmlgraphics.java2d.Graphics2DImagePainter;
 
+import org.apache.fop.fonts.CIDFontType;
 import org.apache.fop.fonts.Font;
 import org.apache.fop.fonts.FontTriplet;
+import org.apache.fop.fonts.FontType;
+import org.apache.fop.fonts.LazyFont;
+import org.apache.fop.fonts.MultiByteFont;
+import org.apache.fop.fonts.Typeface;
 import org.apache.fop.render.ImageHandlerUtil;
 import org.apache.fop.render.RenderingContext;
 import org.apache.fop.render.intermediate.AbstractIFPainter;
 import org.apache.fop.render.intermediate.IFException;
 import org.apache.fop.render.intermediate.IFState;
 import org.apache.fop.render.intermediate.IFUtil;
+import org.apache.fop.render.java2d.CustomFontMetricsMapper;
 import org.apache.fop.render.java2d.FontMetricsMapper;
 import org.apache.fop.render.java2d.Java2DPainter;
+import org.apache.fop.render.pcl.fonts.PCLCharacterWriter;
+import org.apache.fop.render.pcl.fonts.PCLSoftFont;
+import org.apache.fop.render.pcl.fonts.PCLSoftFontManager;
+import org.apache.fop.render.pcl.fonts.truetype.PCLTTFCharacterWriter;
 import org.apache.fop.traits.BorderProps;
 import org.apache.fop.traits.RuleStyle;
 import org.apache.fop.util.CharUtilities;
@@ -73,6 +84,8 @@ public class PCLPainter extends AbstractIFPainter<PCLDocumentHandler> implements
     private Stack<GraphicContext> graphicContextStack = new Stack<GraphicContext>();
     private GraphicContext graphicContext = new GraphicContext();
 
+    private PCLSoftFontManager sfManager = new PCLSoftFontManager();
+
     /**
      * Main constructor.
      * @param parent the parent document handler
@@ -315,16 +328,40 @@ public class PCLPainter extends AbstractIFPainter<PCLDocumentHandler> implements
             //TODO Ignored: state.getFontVariant()
             //TODO Opportunity for font caching if font state is more heavily used
             String fontKey = getFontKey(triplet);
-            boolean pclFont = getPCLUtil().isAllTextAsBitmaps() ? false
-                        : HardcodedFonts.setFont(gen, fontKey, state.getFontSize(), text);
+            Typeface tf = getTypeface(fontKey);
+            boolean drawAsBitmaps = getPCLUtil().isAllTextAsBitmaps();
+            boolean pclFont = HardcodedFonts.setFont(gen, fontKey, state.getFontSize(), text);
             if (pclFont) {
                 drawTextNative(x, y, letterSpacing, wordSpacing, dp, text, triplet);
             } else {
-                drawTextAsBitmap(x, y, letterSpacing, wordSpacing, dp, text, triplet);
-                if (DEBUG) {
-                    state.setTextColor(Color.GRAY);
-                    HardcodedFonts.setFont(gen, "F1", state.getFontSize(), text);
-                    drawTextNative(x, y, letterSpacing, wordSpacing, dp, text, triplet);
+                // TrueType conversion to a soft font (PCL 5 Technical Reference - Chapter 11)
+                if (!drawAsBitmaps && isTrueType(tf)) {
+                    boolean madeSF = false;
+                    if (sfManager.getSoftFont(tf) == null) {
+                        madeSF = true;
+                        ByteArrayOutputStream baos = sfManager.makeSoftFont(tf);
+                        if (baos != null) {
+                            gen.writeBytes(baos.toByteArray());
+                        }
+                    }
+                    int fontID = sfManager.getSoftFontID(tf);
+                    String formattedSize = gen.formatDouble2(state.getFontSize() / 1000.0);
+                    gen.writeCommand(String.format("(s%sV", formattedSize));
+                    gen.writeCommand(String.format("(%dX", fontID));
+                    PCLSoftFont softFont = sfManager.getSoftFont(tf);
+                    PCLCharacterWriter charWriter = new PCLTTFCharacterWriter(softFont);
+                    if (!madeSF) {
+                        gen.writeBytes(sfManager.writeFontIDCommand(fontID));
+                    }
+                    gen.writeBytes(charWriter.writeCharacterDefinitions(text));
+                    drawTextUsingSoftFont(x, y, letterSpacing, wordSpacing, dp, text, triplet, softFont);
+                } else {
+                    drawTextAsBitmap(x, y, letterSpacing, wordSpacing, dp, text, triplet);
+                    if (DEBUG) {
+                        state.setTextColor(Color.GRAY);
+                        HardcodedFonts.setFont(gen, "F1", state.getFontSize(), text);
+                        drawTextNative(x, y, letterSpacing, wordSpacing, dp, text, triplet);
+                    }
                 }
             }
         } catch (IOException ioe) {
@@ -332,6 +369,29 @@ public class PCLPainter extends AbstractIFPainter<PCLDocumentHandler> implements
         }
     }
 
+    private boolean isTrueType(Typeface tf) {
+        if (tf.getFontType().equals(FontType.TRUETYPE)) {
+            return true;
+        } else if (tf instanceof CustomFontMetricsMapper) {
+            Typeface realFont = ((CustomFontMetricsMapper) tf).getRealFont();
+            if (realFont instanceof MultiByteFont) {
+                return ((MultiByteFont) realFont).getCIDType().equals(CIDFontType.CIDTYPE2);
+            }
+        }
+        return false;
+    }
+
+    private Typeface getTypeface(String fontName) {
+        if (fontName == null) {
+            throw new NullPointerException("fontName must not be null");
+        }
+        Typeface tf = getFontInfo().getFonts().get(fontName);
+        if (tf instanceof LazyFont) {
+            tf = ((LazyFont)tf).getRealFont();
+        }
+        return tf;
+    }
+
     private void drawTextNative(int x, int y, int letterSpacing, int wordSpacing, int[][] dp,
             String text, FontTriplet triplet) throws IOException {
         Color textColor = state.getTextColor();
@@ -414,25 +474,86 @@ public class PCLPainter extends AbstractIFPainter<PCLDocumentHandler> implements
 
     }
 
+    private void drawTextUsingSoftFont(int x, int y, int letterSpacing, int wordSpacing, int[][] dp,
+            String text, FontTriplet triplet, PCLSoftFont softFont) throws IOException {
+        Color textColor = state.getTextColor();
+        if (textColor != null) {
+            gen.setTransparencyMode(true, false);
+            gen.selectGrayscale(textColor);
+        }
+
+        setCursorPos(x, y);
+
+        float fontSize = state.getFontSize() / 1000f;
+        Font font = getFontInfo().getFontInstance(triplet, state.getFontSize());
+        int l = text.length();
+        int[] dx = IFUtil.convertDPToDX(dp);
+        int dxl = (dx != null ? dx.length : 0);
+
+        StringBuffer sb = new StringBuffer(Math.max(16, l));
+        if (dx != null && dxl > 0 && dx[0] != 0) {
+            sb.append("\u001B&a+").append(gen.formatDouble2(dx[0] / 100.0)).append('H');
+        }
+        String current = "";
+        for (int i = 0; i < l; i++) {
+            char orgChar = text.charAt(i);
+            float glyphAdjust = 0;
+            if (!font.hasChar(orgChar)) {
+                if (CharUtilities.isFixedWidthSpace(orgChar)) {
+                    //Fixed width space are rendered as spaces so copy/paste works in a reader
+                    char ch = font.mapChar(CharUtilities.SPACE);
+                    int spaceDiff = font.getCharWidth(ch) - font.getCharWidth(orgChar);
+                    glyphAdjust = -(10 * spaceDiff / fontSize);
+                }
+            }
+
+            if ((wordSpacing != 0) && CharUtilities.isAdjustableSpace(orgChar)) {
+                glyphAdjust += wordSpacing;
+            }
+            current += orgChar;
+            glyphAdjust += letterSpacing;
+            if (dx != null && i < dxl - 1) {
+                glyphAdjust += dx[i + 1];
+            }
+
+            if (glyphAdjust != 0) {
+                gen.getOutputStream().write(sb.toString().getBytes(gen.getTextEncoding()));
+                for (int j = 0; j < current.length(); j++) {
+                    gen.getOutputStream().write(
+                            softFont.getUnicodeCodePoint((int) current.charAt(j)));
+                }
+                sb = new StringBuffer();
+
+                String command = (glyphAdjust > 0) ? "\u001B&a+" : "\u001B&a";
+                sb.append(command).append(gen.formatDouble2(glyphAdjust / 100.0)).append('H');
+
+                current = "";
+            }
+        }
+        if (!current.equals("")) {
+            gen.getOutputStream().write(sb.toString().getBytes(gen.getTextEncoding()));
+            for (int i = 0; i < current.length(); i++) {
+                gen.getOutputStream().write(softFont.getUnicodeCodePoint((int) current.charAt(i)));
+            }
+        }
+    }
+
     private static final double SAFETY_MARGIN_FACTOR = 0.05;
 
-    private Rectangle getTextBoundingBox(int x, int y,
-            int letterSpacing, int wordSpacing, int[][] dp,
-            String text,
-            Font font, FontMetricsMapper metrics) {
+    private Rectangle getTextBoundingBox(int x, int y, int letterSpacing, int wordSpacing,
+            int[][] dp, String text, Font font, FontMetricsMapper metrics) {
         int maxAscent = metrics.getMaxAscent(font.getFontSize()) / 1000;
-        int descent = metrics.getDescender(font.getFontSize()) / 1000; //is negative
-        int safetyMargin = (int)(SAFETY_MARGIN_FACTOR * font.getFontSize());
-        Rectangle boundingRect = new Rectangle(
-                x, y - maxAscent - safetyMargin,
-                0, maxAscent - descent + 2 * safetyMargin);
+        int descent = metrics.getDescender(font.getFontSize()) / 1000; // is negative
+        int safetyMargin = (int) (SAFETY_MARGIN_FACTOR * font.getFontSize());
+        Rectangle boundingRect = new Rectangle(x, y - maxAscent - safetyMargin, 0, maxAscent
+                - descent + 2 * safetyMargin);
 
         int l = text.length();
         int[] dx = IFUtil.convertDPToDX(dp);
         int dxl = (dx != null ? dx.length : 0);
 
         if (dx != null && dxl > 0 && dx[0] != 0) {
-            boundingRect.setLocation(boundingRect.x - (int)Math.ceil(dx[0] / 10f), boundingRect.y);
+            boundingRect.setLocation(boundingRect.x - (int) Math.ceil(dx[0] / 10f), boundingRect.y);
         }
         float width = 0.0f;
         for (int i = 0; i < l; i++) {
@@ -451,19 +572,17 @@ public class PCLPainter extends AbstractIFPainter<PCLDocumentHandler> implements
             width += cw + glyphAdjust;
         }
         int extraWidth = font.getFontSize() / 3;
-        boundingRect.setSize(
-                (int)Math.ceil(width) + extraWidth,
-                boundingRect.height);
+        boundingRect.setSize((int) Math.ceil(width) + extraWidth, boundingRect.height);
         return boundingRect;
     }
 
-    private void drawTextAsBitmap(final int x, final int y,
-            final int letterSpacing, final int wordSpacing, final int[][] dp,
-            final String text, FontTriplet triplet) throws IFException {
-        //Use Java2D to paint different fonts via bitmap
+    private void drawTextAsBitmap(final int x, final int y, final int letterSpacing,
+            final int wordSpacing, final int[][] dp, final String text, FontTriplet triplet)
+            throws IFException {
+        // Use Java2D to paint different fonts via bitmap
         final Font font = getFontInfo().getFontInstance(triplet, state.getFontSize());
 
-        //for cursive fonts, so the text isn't clipped
+        // for cursive fonts, so the text isn't clipped
         FontMetricsMapper mapper;
         try {
             mapper = (FontMetricsMapper) getFontInfo().getMetricsFor(font.getFontName());
@@ -473,11 +592,11 @@ public class PCLPainter extends AbstractIFPainter<PCLDocumentHandler> implements
         final int maxAscent = mapper.getMaxAscent(font.getFontSize()) / 1000;
         final int ascent = mapper.getAscender(font.getFontSize()) / 1000;
         final int descent = mapper.getDescender(font.getFontSize()) / 1000;
-        int safetyMargin = (int)(SAFETY_MARGIN_FACTOR * font.getFontSize());
+        int safetyMargin = (int) (SAFETY_MARGIN_FACTOR * font.getFontSize());
         final int baselineOffset = maxAscent + safetyMargin;
 
-        final Rectangle boundingBox = getTextBoundingBox(x, y,
-                letterSpacing, wordSpacing, dp, text, font, mapper);
+        final Rectangle boundingBox = getTextBoundingBox(x, y, letterSpacing, wordSpacing, dp,
+                text, font, mapper);
         final Dimension dim = boundingBox.getSize();
 
         Graphics2DImagePainter painter = new Graphics2DImagePainter() {
@@ -485,7 +604,7 @@ public class PCLPainter extends AbstractIFPainter<PCLDocumentHandler> implements
             public void paint(Graphics2D g2d, Rectangle2D area) {
                 if (DEBUG) {
                     g2d.setBackground(Color.LIGHT_GRAY);
-                    g2d.clearRect(0, 0, (int)area.getWidth(), (int)area.getHeight());
+                    g2d.clearRect(0, 0, (int) area.getWidth(), (int) area.getHeight());
                 }
                 g2d.translate(-x, -y + baselineOffset);
 
@@ -501,7 +620,7 @@ public class PCLPainter extends AbstractIFPainter<PCLDocumentHandler> implements
                 try {
                     painter.drawText(x, y, letterSpacing, wordSpacing, dp, text);
                 } catch (IFException e) {
-                    //This should never happen with the Java2DPainter
+                    // This should never happen with the Java2DPainter
                     throw new RuntimeException("Unexpected error while painting text", e);
                 }
             }
index dcda490595bf7f0405d5b327c90fd3bae7355504..33376655f29e70b446812a310516c0c9af5480b9 100644 (file)
@@ -67,6 +67,7 @@ public class PCLRendererConfigurator extends PrintRendererConfigurator {
         if (config.isTextRendering() != null) {
             pclUtil.setAllTextAsBitmaps(config.isTextRendering());
         }
+
     }
 
     @Override
index 3fc8b5258a1723e12c147786b8c9425bfadc4b1b..32693764f1db8b192eb4d52e88ade1b78dd9916e 100644 (file)
@@ -50,12 +50,6 @@ public class PCLRenderingUtil {
     /** Controls the dithering quality when rendering gray or color images. */
     private float ditheringQuality = 0.5f;
 
-    /**
-     * Controls whether all text should be painted as text. This is a fallback setting in case
-     * the mixture of native and bitmapped text does not provide the necessary quality.
-     */
-    private boolean allTextAsBitmaps;
-
     /**
      * Controls whether an RGB canvas is used when converting Java2D graphics to bitmaps.
      * This can be used to work around problems with Apache Batik, for example, but setting
@@ -68,6 +62,12 @@ public class PCLRenderingUtil {
      */
     private boolean disabledPJL;
 
+    /**
+     * Controls whether all text should be painted as text. This is a fallback setting in case the mixture of native and
+     * bitmapped text does not provide the necessary quality.
+     */
+    private boolean allTextAsBitmaps;
+
     PCLRenderingUtil(FOUserAgent userAgent) {
         this.userAgent = userAgent;
         initialize();
@@ -127,8 +127,7 @@ public class PCLRenderingUtil {
     }
 
     /**
-     * Controls whether all text should be generated as bitmaps or only text for which there's
-     * no native font.
+     * Controls whether all text should be generated as bitmaps or only text for which there's no native font.
      * @param allTextAsBitmaps true if all text should be painted as bitmaps
      */
     public void setAllTextAsBitmaps(boolean allTextAsBitmaps) {
diff --git a/src/java/org/apache/fop/render/pcl/fonts/PCLByteWriterUtil.java b/src/java/org/apache/fop/render/pcl/fonts/PCLByteWriterUtil.java
new file mode 100644 (file)
index 0000000..1b9382f
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * 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.pcl.fonts;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+
+public class PCLByteWriterUtil {
+
+    public byte[] padBytes(byte[] in, int length) {
+        return padBytes(in, length, 0);
+    }
+
+    public byte[] padBytes(byte[] in, int length, int value) {
+        byte[] out = new byte[length];
+        for (int i = 0; i < length; i++) {
+            if (i < in.length) {
+                out[i] = in[i];
+            } else {
+                out[i] = (byte) value;
+            }
+        }
+        return out;
+    }
+
+    public byte[] signedInt(int s) {
+        byte b1 = (byte) (s >> 8);
+        byte b2 = (byte) s;
+        return new byte[]{b1, b2};
+    }
+
+    public byte signedByte(int s) {
+        return (byte) s;
+    }
+
+    public byte[] unsignedLongInt(int s) {
+        return unsignedLongInt((long) s);
+    }
+
+    public byte[] unsignedLongInt(long s) {
+        byte b1 = (byte) ((s >> 24) & 0xff);
+        byte b2 = (byte) ((s >> 16) & 0xff);
+        byte b3 = (byte) ((s >> 8) & 0xff);
+        byte b4 = (byte) (s & 0xff);
+        return new byte[]{b1, b2, b3, b4};
+    }
+
+    public byte[] unsignedInt(int s) {
+        byte b1 = (byte) ((s >> 8) & 0xff);
+        byte b2 = (byte) (s & 0xff);
+        return new byte[]{b1, b2};
+    }
+
+    public int unsignedByte(int b) {
+        return (byte) b & 0xFF;
+    }
+
+    public int maxPower2(int value) {
+        int test = 2;
+        while (test < value) {
+            test *= 2;
+        }
+        return test;
+    }
+
+    public int log(int x, int base) {
+        return (int) (Math.log(x) / Math.log(base));
+    }
+
+    public byte[] toByteArray(int[] s) {
+        byte[] values = new byte[s.length];
+        for (int i = 0; i < s.length; i++) {
+            values[i] = (byte) s[i];
+        }
+        return values;
+    }
+
+    public byte[] insertIntoArray(int index, byte[] insertTo, byte[] data) throws IOException {
+        byte[] preBytes = Arrays.copyOf(insertTo, index);
+        byte[] postBytes = Arrays.copyOfRange(insertTo, index, insertTo.length);
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        baos.write(preBytes);
+        baos.write(data);
+        baos.write(postBytes);
+        return baos.toByteArray();
+    }
+
+    public byte[] updateDataAtLocation(byte[] data, byte[] update, int offset) {
+        int count = 0;
+        for (int i = offset; i < offset + update.length; i++) {
+            data[i] = update[count++];
+        }
+        return data;
+    }
+
+    /**
+     * Writes a PCL escape command to the output stream.
+     * @param cmd the command (without the ESCAPE character)
+     * @throws IOException In case of an I/O error
+     */
+    public byte[] writeCommand(String cmd) throws IOException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        baos.write(27); // ESC
+        baos.write(cmd.getBytes("US-ASCII"));
+        return baos.toByteArray();
+    }
+}
diff --git a/src/java/org/apache/fop/render/pcl/fonts/PCLCharacterDefinition.java b/src/java/org/apache/fop/render/pcl/fonts/PCLCharacterDefinition.java
new file mode 100644 (file)
index 0000000..be1ba7a
--- /dev/null
@@ -0,0 +1,146 @@
+/*
+ * 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.pcl.fonts;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class PCLCharacterDefinition {
+    private int glyphID;
+    private int charCode;
+    private int charDefinitionSize;
+    private byte[] glyfData;
+    private boolean hasContinuation;
+    private PCLCharacterFormat charFormat;
+    private PCLCharacterClass charClass;
+    private PCLByteWriterUtil pclByteWriter;
+    private List<PCLCharacterDefinition> composites;
+
+    public PCLCharacterDefinition(int glyphID, int charCode, PCLCharacterFormat charFormat,
+            PCLCharacterClass charClass, byte[] glyfData, PCLByteWriterUtil pclByteWriter) {
+        this.glyphID = glyphID;
+        this.charCode = charCode;
+        this.charFormat = charFormat;
+        this.charClass = charClass;
+        this.glyfData = glyfData;
+        this.pclByteWriter = pclByteWriter;
+        // Glyph Data + (Descriptor Size) + (Character Data Size) + (Glyph ID) must
+        // be less than 32767 otherwise it will result in a continuation structure.
+        charDefinitionSize = glyfData.length + 4 + 2 + 2;
+        hasContinuation = charDefinitionSize > 32767;
+        composites = new ArrayList<PCLCharacterDefinition>();
+    }
+
+    public byte[] getCharacterCommand() throws IOException {
+        return pclByteWriter.writeCommand(String.format("*c%dE", charCode));
+    }
+
+    public byte[] getCharacterDefinitionCommand() throws IOException {
+        return pclByteWriter.writeCommand(String.format("(s%dW", 10 + glyfData.length));
+    }
+
+    public byte[] getData() throws IOException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+        // Write Character Descriptor
+        if (!hasContinuation) {
+            writeCharacterDescriptorHeader(0, baos);
+            baos.write(glyfData);
+        } else {
+            int continuations = glyfData.length / 32767;
+            for (int i = 0; i < continuations; i++) {
+                writeCharacterDescriptorHeader(i == 0 ? 0 : 1, baos);
+                int continuationStart = i * 32767;
+                int continuationLength = continuationStart - glyfData.length < 32767
+                        ? continuationStart - glyfData.length : 32767;
+                baos.write(glyfData, continuationStart, continuationLength);
+            }
+        }
+        baos.write(0); // Reserved
+        byte[] charBytes = baos.toByteArray();
+        long sum = 0;
+        for (int i = 4; i < charBytes.length; i++) {
+            sum += charBytes[i];
+        }
+        int remainder = (int) (sum % 256);
+        baos.write(256 - remainder); // Checksum
+
+        return baos.toByteArray();
+    }
+
+    private void writeCharacterDescriptorHeader(int continuation, ByteArrayOutputStream baos) throws IOException {
+        baos.write(pclByteWriter.unsignedByte(charFormat.getValue()));
+        baos.write(continuation);
+        baos.write(pclByteWriter.unsignedByte(2)); // Descriptor size (from this byte to character data)
+        baos.write(pclByteWriter.unsignedByte(charClass.getValue()));
+        baos.write(pclByteWriter.unsignedInt(glyfData.length + 4));
+        baos.write(pclByteWriter.unsignedInt(glyphID));
+    }
+
+    public void addCompositeGlyph(PCLCharacterDefinition composite) {
+        composites.add(composite);
+    }
+
+    public List<PCLCharacterDefinition> getCompositeGlyphs() {
+        return composites;
+    }
+
+    /**
+     * Character Format used in PCL Character Descriptor See Table 11-50 from PCL 5 Specification
+     */
+    public enum PCLCharacterFormat {
+        LaserJet_Raster(4),
+        Intellifont(10),
+        TrueType(15);
+
+        private int value;
+
+        PCLCharacterFormat(int value) {
+            this.value = value;
+        }
+
+        public int getValue() {
+            return value;
+        }
+    }
+
+    /**
+     * Character Class used in PCL Character Descriptor See Table 11-51 from PCL 5 Specification
+     */
+    public enum PCLCharacterClass {
+        Bitmap(1),
+        CompressedBitmap(2),
+        Contour_Intellifont(3),
+        Compound_Contour_Intellifont(4),
+        TrueType(15);
+
+        private int value;
+
+        PCLCharacterClass(int value) {
+            this.value = value;
+        }
+
+        public int getValue() {
+            return value;
+        }
+    }
+}
diff --git a/src/java/org/apache/fop/render/pcl/fonts/PCLCharacterWriter.java b/src/java/org/apache/fop/render/pcl/fonts/PCLCharacterWriter.java
new file mode 100644 (file)
index 0000000..df04371
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * 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.pcl.fonts;
+
+import java.io.IOException;
+
+import org.apache.fop.fonts.truetype.FontFileReader;
+import org.apache.fop.fonts.truetype.OpenFont;
+
+public abstract class PCLCharacterWriter {
+
+    protected PCLSoftFont font;
+    protected PCLByteWriterUtil pclByteWriter;
+    protected OpenFont openFont;
+    protected FontFileReader fontReader;
+
+    public PCLCharacterWriter(PCLSoftFont font) throws IOException {
+        this.font = font;
+        openFont = font.getOpenFont();
+        fontReader = font.getReader();
+        pclByteWriter = new PCLByteWriterUtil();
+    }
+
+    public abstract byte[] writeCharacterDefinitions(String text) throws IOException;
+}
diff --git a/src/java/org/apache/fop/render/pcl/fonts/PCLFontReader.java b/src/java/org/apache/fop/render/pcl/fonts/PCLFontReader.java
new file mode 100644 (file)
index 0000000..22e9760
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * 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.pcl.fonts;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.fop.fonts.Typeface;
+import org.apache.fop.fonts.truetype.FontFileReader;
+import org.apache.fop.fonts.truetype.OpenFont;
+
+public abstract class PCLFontReader {
+
+    protected Typeface typeface;
+    protected PCLByteWriterUtil pclByteWriter;
+
+    public PCLFontReader(Typeface font, PCLByteWriterUtil pclByteWriter) {
+        this.typeface = font;
+        this.pclByteWriter = pclByteWriter;
+    }
+
+    /** Header Data **/
+    public abstract int getDescriptorSize();
+    public abstract int getHeaderFormat();
+    public abstract int getFontType();
+    public abstract int getStyleMSB();
+    public abstract int getBaselinePosition();
+    public abstract int getCellWidth();
+    public abstract int getCellHeight();
+    public abstract int getOrientation();
+    public abstract int getSpacing();
+    public abstract int getSymbolSet();
+    public abstract int getPitch();
+    public abstract int getHeight();
+    public abstract int getXHeight();
+    public abstract int getWidthType();
+    public abstract int getStyleLSB();
+    public abstract int getStrokeWeight();
+    public abstract int getTypefaceLSB();
+    public abstract int getTypefaceMSB();
+    public abstract int getSerifStyle();
+    public abstract int getQuality();
+    public abstract int getPlacement();
+    public abstract int getUnderlinePosition();
+    public abstract int getUnderlineThickness();
+    public abstract int getTextHeight();
+    public abstract int getTextWidth();
+    public abstract int getFirstCode();
+    public abstract int getLastCode();
+    public abstract int getPitchExtended();
+    public abstract int getHeightExtended();
+    public abstract int getCapHeight();
+    public abstract int getFontNumber();
+    public abstract String getFontName();
+    public abstract int getScaleFactor() throws IOException;
+    public abstract int getMasterUnderlinePosition() throws IOException;
+    public abstract int getMasterUnderlineThickness() throws IOException;
+    public abstract int getFontScalingTechnology();
+    public abstract int getVariety();
+
+    /** Segmented Font Data **/
+    public abstract List<PCLFontSegment> getFontSegments() throws IOException;
+
+    /** Character Definitions **/
+    public abstract Map<Integer, int[]> getCharacterOffsets() throws IOException;
+
+    public abstract OpenFont getFontFile();
+    public abstract FontFileReader getFontFileReader();
+
+    /**
+     * Gets the most significant byte from a 16-bit integer
+     * @param s The number
+     * @return The resulting byte value as an integer
+     */
+    protected int getMSB(int s) {
+        return s >> 8;
+    }
+
+    /**
+     * Gets the least significant byte from a 16-bit integer
+     * @param s The number
+     * @return The resulting byte value as an integer
+     */
+    protected int getLSB(int s) {
+        byte b1 = (byte) (s >> 8);
+        return s;
+    }
+}
diff --git a/src/java/org/apache/fop/render/pcl/fonts/PCLFontReaderFactory.java b/src/java/org/apache/fop/render/pcl/fonts/PCLFontReaderFactory.java
new file mode 100644 (file)
index 0000000..15c4e8d
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * 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.pcl.fonts;
+
+import java.io.IOException;
+
+import org.apache.fop.fonts.CIDFontType;
+import org.apache.fop.fonts.CustomFont;
+import org.apache.fop.fonts.FontType;
+import org.apache.fop.fonts.MultiByteFont;
+import org.apache.fop.fonts.Typeface;
+import org.apache.fop.render.java2d.CustomFontMetricsMapper;
+import org.apache.fop.render.pcl.fonts.truetype.PCLTTFFontReader;
+
+public final class PCLFontReaderFactory {
+
+    private PCLByteWriterUtil pclByteWriter;
+
+    private PCLFontReaderFactory(PCLByteWriterUtil pclByteWriter) {
+        this.pclByteWriter = pclByteWriter;
+    }
+
+    public static PCLFontReaderFactory getInstance(PCLByteWriterUtil pclByteWriter) {
+        return new PCLFontReaderFactory(pclByteWriter);
+    }
+
+    public PCLFontReader createInstance(Typeface font) throws IOException {
+        if (font.getFontType() == FontType.TRUETYPE || isCIDType2(font)) {
+            return new PCLTTFFontReader(font, pclByteWriter);
+        }
+        // else if (font instanceof MultiByteFont && ((MultiByteFont) font).isOTFFile()) {
+            // Placeholder for future Type 1 / OTF Soft font implementations e.g.
+            // return new PCLOTFFontReader(font, pclByteWriter);
+        // }
+        return null;
+    }
+
+    private boolean isCIDType2(Typeface font) {
+        CustomFontMetricsMapper fontMetrics = (CustomFontMetricsMapper) font;
+        CustomFont customFont = (CustomFont) fontMetrics.getRealFont();
+
+        if (customFont instanceof MultiByteFont) {
+            return ((MultiByteFont) customFont).getCIDType() == CIDFontType.CIDTYPE2;
+        }
+        return false;
+    }
+
+}
diff --git a/src/java/org/apache/fop/render/pcl/fonts/PCLFontSegment.java b/src/java/org/apache/fop/render/pcl/fonts/PCLFontSegment.java
new file mode 100644 (file)
index 0000000..111dbf9
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * 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.pcl.fonts;
+
+public class PCLFontSegment {
+    private SegmentID identifier;
+    private byte[] data;
+
+    public PCLFontSegment(SegmentID identifier, byte[] data) {
+        this.identifier = identifier;
+        this.data = data;
+    }
+
+    public byte[] getData() {
+        return data;
+    }
+
+    public SegmentID getIdentifier() {
+        return identifier;
+    }
+
+    public int getSize() {
+        return (identifier == SegmentID.NULL) ? 0 : data.length;
+    }
+
+    public enum SegmentID {
+        CC(17219), // Character Complement
+        CP(17232), // Copyright
+        GT(18260), // Global TrueType Data
+        IF(18758), // Intellifont Face Data
+        PA(20545), // PANOSE Description
+        XW(22619), // XWindows Font Name
+        NULL(65535); // Null Segment
+
+        private int complementID;
+
+        SegmentID(int complementID) {
+            this.complementID = complementID;
+        }
+
+        public int getValue() {
+            return complementID;
+        }
+    }
+}
diff --git a/src/java/org/apache/fop/render/pcl/fonts/PCLSoftFont.java b/src/java/org/apache/fop/render/pcl/fonts/PCLSoftFont.java
new file mode 100644 (file)
index 0000000..87f3998
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * 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.pcl.fonts;
+
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.fop.fonts.MultiByteFont;
+import org.apache.fop.fonts.Typeface;
+import org.apache.fop.fonts.truetype.FontFileReader;
+import org.apache.fop.fonts.truetype.OpenFont;
+import org.apache.fop.render.java2d.CustomFontMetricsMapper;
+
+public class PCLSoftFont {
+    private int fontID;
+    private Typeface font;
+    private Map<Integer, int[]> charOffsets;
+    private OpenFont openFont;
+    private InputStream fontStream;
+    private FontFileReader reader;
+    /** Map containing unicode character and it's soft font codepoint **/
+    private Map<Integer, Integer> charsWritten;
+    private Map<Integer, Integer> charMtxPositions;
+    private int charCount = 32;
+
+    public PCLSoftFont(int fontID, Typeface font) {
+        this.fontID = fontID;
+        this.font = font;
+        charsWritten = new HashMap<Integer, Integer>();
+    }
+
+    public Typeface getTypeface() {
+        return font;
+    }
+
+    public int getFontID() {
+        return fontID;
+    }
+
+    public void setCharacterOffsets(Map<Integer, int[]> charOffsets) {
+        this.charOffsets = charOffsets;
+    }
+
+    public Map<Integer, int[]> getCharacterOffsets() {
+        return charOffsets;
+    }
+
+    public OpenFont getOpenFont() {
+        return openFont;
+    }
+
+    public void setOpenFont(OpenFont openFont) {
+        this.openFont = openFont;
+    }
+
+    public InputStream getFontStream() {
+        return fontStream;
+    }
+
+    public void setFontStream(InputStream fontStream) {
+        this.fontStream = fontStream;
+    }
+
+    public FontFileReader getReader() {
+        return reader;
+    }
+
+    public void setReader(FontFileReader reader) {
+        this.reader = reader;
+    }
+
+    public void writeCharacter(int unicode) {
+        charsWritten.put(unicode, charCount++);
+    }
+
+    public int getUnicodeCodePoint(int unicode) {
+        return charsWritten.get(unicode);
+    }
+
+    public boolean hasPreviouslyWritten(int unicode) {
+        return charsWritten.containsKey(unicode);
+    }
+
+    public int getMtxCharIndex(int unicode) {
+        if (charMtxPositions.get(unicode) != null) {
+            return charMtxPositions.get(unicode);
+        }
+        return 0;
+    }
+
+    public int getCmapGlyphIndex(int unicode) {
+        if (font instanceof CustomFontMetricsMapper) {
+            CustomFontMetricsMapper customFont = (CustomFontMetricsMapper) font;
+            Typeface realFont = customFont.getRealFont();
+            if (realFont instanceof MultiByteFont) {
+                MultiByteFont mbFont = (MultiByteFont) realFont;
+                return mbFont.findGlyphIndex(unicode);
+            }
+        }
+        return 0;
+    }
+
+    public void setMtxCharIndexes(Map<Integer, Integer> charMtxPositions) {
+        this.charMtxPositions = charMtxPositions;
+    }
+
+    public int getCharCount() {
+        return charCount;
+    }
+}
diff --git a/src/java/org/apache/fop/render/pcl/fonts/PCLSoftFontManager.java b/src/java/org/apache/fop/render/pcl/fonts/PCLSoftFontManager.java
new file mode 100644 (file)
index 0000000..77da40a
--- /dev/null
@@ -0,0 +1,170 @@
+/*
+ * 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.pcl.fonts;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fop.fonts.Typeface;
+
+public class PCLSoftFontManager {
+    private ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    private PCLFontReader fontReader;
+    private PCLByteWriterUtil pclByteWriter = new PCLByteWriterUtil();
+    private int byte64Offset;
+    private List<PCLSoftFont> fonts = new ArrayList<PCLSoftFont>();
+    private PCLFontReaderFactory fontReaderFactory;
+
+    public ByteArrayOutputStream makeSoftFont(Typeface font) throws IOException {
+        PCLSoftFont softFont = new PCLSoftFont(fonts.size() + 1, font);
+        fontReaderFactory = PCLFontReaderFactory.getInstance(pclByteWriter);
+        fontReader = fontReaderFactory.createInstance(font);
+        if (fontReader != null) {
+            initialize();
+            assignFontID();
+            writeFontHeader();
+            softFont.setCharacterOffsets(fontReader.getCharacterOffsets());
+            softFont.setOpenFont(fontReader.getFontFile());
+            softFont.setReader(fontReader.getFontFileReader());
+            fonts.add(softFont);
+            return baos;
+        } else {
+            return null;
+        }
+    }
+
+    private void initialize() {
+        baos.reset();
+    }
+
+    private void assignFontID() throws IOException {
+        baos.write(pclByteWriter.writeCommand(String.format("*c%dD", fonts.size() + 1)));
+    }
+
+    private void writeFontHeader() throws IOException {
+        ByteArrayOutputStream header = new ByteArrayOutputStream();
+        header.write(pclByteWriter.unsignedInt(fontReader.getDescriptorSize()));
+        header.write(pclByteWriter.unsignedByte(fontReader.getHeaderFormat()));
+        header.write(pclByteWriter.unsignedByte(fontReader.getFontType()));
+        header.write(pclByteWriter.unsignedByte(fontReader.getStyleMSB()));
+        header.write(0); // Reserved
+        header.write(pclByteWriter.unsignedInt(fontReader.getBaselinePosition()));
+        header.write(pclByteWriter.unsignedInt(fontReader.getCellWidth()));
+        header.write(pclByteWriter.unsignedInt(fontReader.getCellHeight()));
+        header.write(pclByteWriter.unsignedByte(fontReader.getOrientation()));
+        header.write(fontReader.getSpacing());
+        header.write(pclByteWriter.unsignedInt(fontReader.getSymbolSet()));
+        header.write(pclByteWriter.unsignedInt(fontReader.getPitch()));
+        header.write(pclByteWriter.unsignedInt(fontReader.getHeight()));
+        header.write(pclByteWriter.unsignedInt(fontReader.getXHeight()));
+        header.write(pclByteWriter.signedByte(fontReader.getWidthType()));
+        header.write(pclByteWriter.unsignedByte(fontReader.getStyleLSB()));
+        header.write(pclByteWriter.signedByte(fontReader.getStrokeWeight()));
+        header.write(pclByteWriter.unsignedByte(fontReader.getTypefaceLSB()));
+        header.write(pclByteWriter.unsignedByte(fontReader.getTypefaceMSB()));
+        header.write(pclByteWriter.unsignedByte(fontReader.getSerifStyle()));
+        header.write(pclByteWriter.unsignedByte(fontReader.getQuality()));
+        header.write(pclByteWriter.signedByte(fontReader.getPlacement()));
+        header.write(pclByteWriter.signedByte(fontReader.getUnderlinePosition()));
+        header.write(pclByteWriter.unsignedByte(fontReader.getUnderlineThickness()));
+        header.write(pclByteWriter.unsignedInt(fontReader.getTextHeight()));
+        header.write(pclByteWriter.unsignedInt(fontReader.getTextWidth()));
+        header.write(pclByteWriter.unsignedInt(fontReader.getFirstCode()));
+        header.write(pclByteWriter.unsignedInt(fontReader.getLastCode()));
+        header.write(pclByteWriter.unsignedByte(fontReader.getPitchExtended()));
+        header.write(pclByteWriter.unsignedByte(fontReader.getHeightExtended()));
+        header.write(pclByteWriter.unsignedInt(fontReader.getCapHeight()));
+        header.write(pclByteWriter.unsignedLongInt(fontReader.getFontNumber()));
+        header.write(pclByteWriter.padBytes(fontReader.getFontName().getBytes("US-ASCII"), 16, 32));
+        // Byte 64 starting point stored for checksum
+        byte64Offset = header.size();
+        header.write(pclByteWriter.unsignedInt(fontReader.getScaleFactor()));
+        header.write(pclByteWriter.signedInt(fontReader.getMasterUnderlinePosition()));
+        header.write(pclByteWriter.unsignedInt(fontReader.getMasterUnderlineThickness()));
+        header.write(pclByteWriter.unsignedByte(fontReader.getFontScalingTechnology()));
+        header.write(pclByteWriter.unsignedByte(fontReader.getVariety()));
+
+        writeSegmentedFontData(header, byte64Offset);
+
+        baos.write(getFontHeaderCommand(header.size()));
+        baos.write(header.toByteArray());
+    }
+
+    private void writeSegmentedFontData(ByteArrayOutputStream header, int byte64Offset) throws IOException {
+        List<PCLFontSegment> fontSegments = fontReader.getFontSegments();
+        for (PCLFontSegment segment : fontSegments) {
+            writeFontSegment(header, segment);
+        }
+        header.write(0); // Reserved
+        // Checksum must equal 0 when added to byte 64 offset (modulo 256)
+        long sum = 0;
+        byte[] headerBytes = header.toByteArray();
+        for (int i = 64; i < headerBytes.length; i++) {
+            sum += headerBytes[i];
+        }
+        int remainder = (int) (sum % 256);
+        header.write(256 - remainder);
+    }
+
+    private byte[] getFontHeaderCommand(int headerSize) throws IOException {
+        return pclByteWriter.writeCommand(String.format(")s%dW", headerSize));
+    }
+
+    private void writeFontSegment(ByteArrayOutputStream header, PCLFontSegment segment) throws IOException {
+        header.write(pclByteWriter.unsignedInt(segment.getIdentifier().getValue()));
+        header.write(pclByteWriter.unsignedInt(segment.getData().length));
+        header.write(segment.getData());
+    }
+
+    public List<PCLSoftFont> getSoftFonts() {
+        return fonts;
+    }
+
+    /**
+     * Finds a soft font associated with the given typeface. If more than one instance of the font exists (as each font
+     * is bound and restricted to 255 characters) it will find the last font with available capacity.
+     * @param font The typeface associated with the soft font
+     * @return Returns the PCLSoftFont with available capacity
+     */
+    public PCLSoftFont getSoftFont(Typeface font) {
+        for (PCLSoftFont sftFont : fonts) {
+            if (sftFont.getTypeface().equals(font) && sftFont.getCharCount() < 255) {
+                return sftFont;
+            }
+        }
+        return null;
+    }
+
+    public int getSoftFontID(Typeface tf) throws IOException {
+        PCLSoftFont font = getSoftFont(tf);
+        for (int i = 0; i < fonts.size(); i++) {
+            if (fonts.get(i).equals(font)) {
+                return i + 1;
+            }
+        }
+        return -1;
+    }
+
+    public byte[] writeFontIDCommand(int fontID) throws IOException {
+        return pclByteWriter.writeCommand(String.format("*c%dD", fontID));
+    }
+}
diff --git a/src/java/org/apache/fop/render/pcl/fonts/PCLSymbolSet.java b/src/java/org/apache/fop/render/pcl/fonts/PCLSymbolSet.java
new file mode 100644 (file)
index 0000000..a2c50df
--- /dev/null
@@ -0,0 +1,200 @@
+/*
+ * 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.pcl.fonts;
+
+/**
+ * Table C-1 from http://www.lprng.com/DISTRIB/RESOURCES/DOCS/pcl5comp.pdf
+ */
+public enum PCLSymbolSet {
+    // Unbound font containing > 256 characters
+    Unbound("1X", 56),
+
+    // Other symbol sets to use in bound fonts
+    Bound_Generic("0Q", 17),
+    GW_3212("18C", 597),
+    ISO_60_Danish_Norwegian("0D", 4),
+    Devanagari("2D", 68),
+    ISO_4_United_Kingdom("1E", 37),
+    Windows_3_1_Latin2("9E", 293),
+    ISO_69_French("1F", 38),
+    ISO_21_German("1G", 39),
+    Greek_8("8G", 283),
+    Windows_3_1_Latin_Greek("9G", 295),
+    PC_851_Latin_Greek("10G", 327),
+    PC_8_Latin_Greek("12G", 391),
+    Hebrew_7("0H", 8),
+    ISO_8859_8_Latin_Hebrew("7H", 232),
+    Hebrew_8("8H", 264),
+    PC_862_Latin_Hebrew("15H", 488),
+    ISO_15_Italian("0I", 9),
+    Microsoft_Publishing("6J", 202),
+    DeskTop("7J", 234),
+    Document("8J", 266),
+    PC_1004("9J", 298),
+    PS_Text("10J", 330),
+    PS_ISO_Latin1("11J", 362),
+    MC_Text("12J", 394),
+    Ventura_International3("13J", 426),
+    Ventura_US3("14J", 458),
+    Swash_Characters("16J", 522),
+    Small_Caps_Old_Style_Figures("17J", 554),
+    Old_Style_Figures("18J", 586),
+    Fractions("19J", 618),
+    Lining_Figures("21J", 682),
+    Small_Caps_and_Lining_Figures("22J", 714),
+    Alternate_Caps("23J", 746),
+    Kana_8_JIS_210("8K", 267),
+    Korean_8("9K", 299),
+
+    Line_Draw_7("0L", 12),
+    HP_Block_Characters("1L", 44),
+    Tax_Line_Draw("2L", 76),
+    Line_Draw_8("8L", 268),
+    Ventura_ITC_Zapf_Dingbats3("9L", 300),
+    PS_ITC_Zapf_Dingbats("10L", 332),
+    ITC_Zapf_Dingbats_Series_100("11L", 364),
+    ITC_Zapf_Dingbats_Series_200("12L", 396),
+    ITC_Zapf_Dingbats_Series_300("13L", 428),
+    Windows_Baltic("19L", 620),
+    Carta("20L", 652),
+    Ornaments("21L", 684),
+    Universal_News_Commercial_Pi("22L", 716),
+    Chess("23L", 748),
+    Astrology_1("24L", 780),
+    Pi_Set_1("31L", 1004),
+    Pi_Set_2("32L", 1036),
+    Pi_Set_3("33L", 1068),
+    Pi_Set_4("34L", 1100),
+    Pi_Set_5("35L", 1132),
+    Pi_Set_6("36L", 1164),
+    Wingdings("579L", 18540),
+    Math_7("0M", 13),
+    Tech_7("1M", 45),
+    PS_Math("5M", 173),
+    Ventura_Math3("6M", 205),
+    Math_8("8M", 269),
+    Universal_Greek_Math_Pi("10M", 333),
+    TeX_Math_Extension("11M", 365),
+    TeX_Math_Symbol("12M", 397),
+    TeX_Math_Italic("13M", 429),
+    Symbol("19M", 621),
+    ISO_8859_1_Latin_1("0N", 14),
+    ISO_8859_2_Latin_2("2N", 78),
+
+    ISO_8859_3_Latin_3("3N", 110),
+    ISO_8859_4_Latin_4("4N", 142),
+    ISO_8859_9_Latin_5("5N", 174),
+    ISO_8859_10_Latin_6("6N", 206),
+    ISO_8859_5_Latin_Cyrillic("10N", 334),
+    ISO_8859_6_Latin_Arabic("11N", 366),
+    ISO_8859_7_Latin_Greek("12N", 398),
+    OCR_A("0O", 15),
+    OCR_B("1O", 47),
+    OCR_M("2O", 79),
+    MICR_E13B("10O", 335),
+    Typewriter_Paired_APL("0P", 16),
+    Bit_Paired_APL("1P", 48),
+    Expert("10P", 336),
+    Alternate("11P", 368),
+    Fraktur("12P", 400),
+    Cyrillic_ASCII_8859_5_1986("0R", 18),
+    Cyrillic("1R", 50),
+    PC_Cyrillic("3R", 114),
+    Windows_3_1_Latin_Cyrillic("9R", 306),
+    ISO_11_Swedish("0S", 19),
+    ISO_17_Spanish3("2S", 83),
+    HP_European_Spanish("7S", 243),
+    HP_Latin_Spanish("8S", 275),
+    HP_GL_Download("16S", 531),
+    HP_GL_Drafting("17S", 563),
+    HP_GL_Special_Symbols("18S", 595),
+    Sonata("20S", 659),
+    Thai_8("0T", 20),
+    TISI_620_2533_Thai("1T", 52),
+    Windows_3_1_Latin_5("5T", 180),
+    Turkish_8("8T", 276),
+
+    PC_8_Turkish("9T", 308),
+    Teletex("10T", 340),
+    ISO_6_ASCII("0U", 21),
+    Legal("1U", 53),
+    HPL("5U", 181),
+    OEM_1("7U", 245),
+    Roman_8("8U", 277),
+    Windows_3_0_Latin_1("9U", 309),
+    PC_8_Code_Page_437("10U", 341),
+    PC_8_D_N_Danish_Norwegian("11U", 373),
+    PC_850_Multilingual("12U", 405),
+    Pi_Font("15U", 501),
+    PC_857("16U", 533),
+    PC_852_Latin_2("17U", 565),
+    Windows_3_1_Latin_1("19U", 629),
+    PC_860_Portugal("20U", 661),
+    PC_861_Iceland("21U", 693),
+    PC_863_Canada_French("23U", 757),
+    PC_865_Norway("25U", 821),
+    PC_775("26U", 853),
+    Arabic_8("8V", 278),
+    Windows_3_1_Latin_Arabic("9V", 310),
+    Code_Page_864_Latin_Arabic("10V", 342),
+    Barcode_3of9("0Y", 25),
+    Industrial_2_of_5_Barcode("1Y", 57),
+    Matrix_2_of_5_Barcode("2Y", 89),
+    Interleaved_2_of_5_Barcode("4Y", 153),
+    CODABAR_Barcode("5Y", 185),
+    MSI_Plessey_Barcode("6Y", 217),
+    Code_11_Barcode("7Y", 249),
+    UPC_EAN_Barcode("8Y", 281),
+    MICR_CMC_7("14Y", 473),
+    USPS_ZIP("5Y", 505),
+
+    Math_7_2("0A", 1),
+    Line_Draw_7_2("0B", 2),
+    HP_Large_Characters("0C", 3),
+    ISO_61_Norwegian_Version_2("1D", 36),
+    Roman_Extension("0E", 5),
+    ISO_25_French("0F", 6),
+    HP_German("0G", 7),
+    ISO_14_JIS_ASCII("0K", 11),
+    ISO_13_Katakana("1K", 43),
+    ISO_57_Chinese("2K", 75),
+    HP_Spanish("1S", 51),
+    ISO_10_Swedish("3S", 115),
+    ISO_16_Portuguese("4S", 147),
+    ISO_84_Portuguese("5S", 179),
+    ISO_85_Spanish("6S", 211),
+    ISO_2_International_Reference("2U", 85),
+    Arabic("0V", 22);
+
+    private String symbolSetID;
+    private int kind1;
+
+    PCLSymbolSet(String symbolSetID, int kind1) {
+        this.kind1 = kind1;
+    }
+
+    public String getSymbolSetID() {
+        return symbolSetID;
+    }
+
+    public int getKind1() {
+        return kind1;
+    }
+}
diff --git a/src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFCharacterWriter.java b/src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFCharacterWriter.java
new file mode 100644 (file)
index 0000000..0d2eaf4
--- /dev/null
@@ -0,0 +1,152 @@
+/*
+ * 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.pcl.fonts.truetype;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.fop.fonts.truetype.GlyfTable;
+import org.apache.fop.fonts.truetype.OFDirTabEntry;
+import org.apache.fop.fonts.truetype.OFMtxEntry;
+import org.apache.fop.fonts.truetype.OFTableName;
+import org.apache.fop.fonts.truetype.TTFFile;
+import org.apache.fop.render.pcl.fonts.PCLCharacterDefinition;
+import org.apache.fop.render.pcl.fonts.PCLCharacterDefinition.PCLCharacterClass;
+import org.apache.fop.render.pcl.fonts.PCLCharacterDefinition.PCLCharacterFormat;
+import org.apache.fop.render.pcl.fonts.PCLCharacterWriter;
+import org.apache.fop.render.pcl.fonts.PCLSoftFont;
+
+public class PCLTTFCharacterWriter extends PCLCharacterWriter {
+
+    private List<OFMtxEntry> mtx;
+    private OFDirTabEntry tabEntry;
+
+    public PCLTTFCharacterWriter(PCLSoftFont softFont) throws IOException {
+        super(softFont);
+        softFont.setMtxCharIndexes(scanMtxCharacters());
+    }
+
+    @Override
+    public byte[] writeCharacterDefinitions(String text) throws IOException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        for (char ch : text.toCharArray()) {
+            int character = (int) ch;
+            if (!font.hasPreviouslyWritten(character)) {
+                PCLCharacterDefinition pclChar = getCharacterDefinition(ch);
+                writePCLCharacter(baos, pclChar);
+                List<PCLCharacterDefinition> compositeGlyphs = pclChar.getCompositeGlyphs();
+                for (PCLCharacterDefinition composite : compositeGlyphs) {
+                    writePCLCharacter(baos, composite);
+                }
+            }
+        }
+        return baos.toByteArray();
+    }
+
+    private void writePCLCharacter(ByteArrayOutputStream baos, PCLCharacterDefinition pclChar) throws IOException {
+        baos.write(pclChar.getCharacterCommand());
+        baos.write(pclChar.getCharacterDefinitionCommand());
+        baos.write(pclChar.getData());
+    }
+
+    private Map<Integer, Integer> scanMtxCharacters() throws IOException {
+        Map<Integer, Integer> charMtxOffsets = new HashMap<Integer, Integer>();
+        List<OFMtxEntry> mtx = openFont.getMtx();
+        OFTableName glyfTag = OFTableName.GLYF;
+        if (openFont.seekTab(fontReader, glyfTag, 0)) {
+            for (int i = 1; i < mtx.size(); i++) {
+                OFMtxEntry entry = mtx.get(i);
+                int charCode = 0;
+                if (entry.getUnicodeIndex().size() > 0) {
+                    charCode = (Integer) entry.getUnicodeIndex().get(0);
+                } else {
+                    charCode = entry.getIndex();
+                }
+                charMtxOffsets.put(charCode, i);
+            }
+        }
+        return charMtxOffsets;
+    }
+
+    private PCLCharacterDefinition getCharacterDefinition(int unicode) throws IOException {
+        if (mtx == null) {
+            mtx = openFont.getMtx();
+            tabEntry = openFont.getDirectoryEntry(OFTableName.GLYF);
+        }
+        if (openFont.seekTab(fontReader, OFTableName.GLYF, 0)) {
+            int charIndex = font.getMtxCharIndex(unicode);
+
+            // Fallback - only works for MultiByte fonts
+            if (charIndex == 0) {
+                charIndex = font.getCmapGlyphIndex(unicode);
+            }
+
+            Map<Integer, Integer> subsetGlyphs = new HashMap<Integer, Integer>();
+            subsetGlyphs.put(charIndex, 1);
+
+            byte[] glyphData = getGlyphData(charIndex);
+
+            font.writeCharacter(unicode);
+
+            PCLCharacterDefinition newChar = new PCLCharacterDefinition(charIndex, font.getUnicodeCodePoint(unicode),
+                    PCLCharacterFormat.TrueType,
+                    PCLCharacterClass.TrueType, glyphData, pclByteWriter);
+
+            // Handle composite character definitions
+            GlyfTable glyfTable = new GlyfTable(fontReader, mtx.toArray(new OFMtxEntry[mtx.size()]),
+                    tabEntry, subsetGlyphs);
+            if (glyfTable.isComposite(charIndex)) {
+                Set<Integer> composite = glyfTable.retrieveComposedGlyphs(charIndex);
+                for (Integer compositeIndex : composite) {
+                    byte[] compositeData = getGlyphData(compositeIndex);
+                    newChar.addCompositeGlyph(new PCLCharacterDefinition(compositeIndex, 65535,
+                            PCLCharacterFormat.TrueType, PCLCharacterClass.TrueType, compositeData, pclByteWriter));
+                }
+            }
+
+            return newChar;
+        }
+        return null;
+    }
+
+    private byte[] getGlyphData(int charIndex) throws IOException {
+        OFMtxEntry entry = mtx.get(charIndex);
+        OFMtxEntry nextEntry;
+        int nextOffset = 0;
+        if (charIndex < mtx.size() - 1) {
+            nextEntry = mtx.get(charIndex + 1);
+            nextOffset = (int) nextEntry.getOffset();
+        } else {
+            nextOffset = (int) ((TTFFile) openFont).getLastGlyfLocation();
+        }
+        int glyphOffset = (int) entry.getOffset();
+        int glyphLength = nextOffset - glyphOffset;
+
+        byte[] glyphData = new byte[0];
+        if (glyphLength > 0) {
+            glyphData = fontReader.getBytes((int) tabEntry.getOffset() + glyphOffset, glyphLength);
+        }
+        return glyphData;
+    }
+}
diff --git a/src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFFontReader.java b/src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFFontReader.java
new file mode 100644 (file)
index 0000000..c52d2d5
--- /dev/null
@@ -0,0 +1,619 @@
+/*
+ * 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.pcl.fonts.truetype;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.fop.fonts.CustomFont;
+import org.apache.fop.fonts.Typeface;
+import org.apache.fop.fonts.truetype.FontFileReader;
+import org.apache.fop.fonts.truetype.OFDirTabEntry;
+import org.apache.fop.fonts.truetype.OFFontLoader;
+import org.apache.fop.fonts.truetype.OFMtxEntry;
+import org.apache.fop.fonts.truetype.OFTableName;
+import org.apache.fop.fonts.truetype.OpenFont;
+import org.apache.fop.fonts.truetype.TTFFile;
+import org.apache.fop.render.java2d.CustomFontMetricsMapper;
+import org.apache.fop.render.pcl.fonts.PCLByteWriterUtil;
+import org.apache.fop.render.pcl.fonts.PCLFontReader;
+import org.apache.fop.render.pcl.fonts.PCLFontSegment;
+import org.apache.fop.render.pcl.fonts.PCLFontSegment.SegmentID;
+import org.apache.fop.render.pcl.fonts.PCLSymbolSet;
+
+public class PCLTTFFontReader extends PCLFontReader {
+    protected TTFFile ttfFont;
+    protected InputStream fontStream;
+    protected FontFileReader reader;
+    private PCLTTFPCLTFontTable pcltTable;
+    private PCLTTFOS2FontTable os2Table;
+    private PCLTTFPOSTFontTable postTable;
+    private PCLTTFTableFactory ttfTableFactory;
+
+    private static final int HMTX_RESTRICT_SIZE = 50000;
+
+    private static final Map<Integer, Integer> FONT_WEIGHT = new HashMap<Integer, Integer>() {
+        private static final long serialVersionUID = 1L;
+        {
+            put(100, -6); // 100 Thin
+            put(200, -4); // 200 Extra-Light
+            put(300, -3); // 300 Light
+            put(400, 0); // 400 Normal (Regular)
+            put(500, 0); // 500 Medium
+            put(600, 2); // 600 Semi-bold
+            put(700, 3); // 700 Bold
+            put(800, 4); // 800 Extra-bold
+            put(900, 5); // 900 Black (Heavy)
+        }
+    };
+
+    private static final Map<Integer, Integer> FONT_SERIF = new HashMap<Integer, Integer>() {
+        private static final long serialVersionUID = 1L;
+        {
+            /** The following are the best guess conversion between serif styles. Unfortunately
+             *  there appears to be no standard and so each specification has it's own set of values.
+             *  Please change if better fit found. **/
+            put(0, 0); // Any = Normal Sans
+            put(1, 64); // No Fit = Sans Serif
+            put(2, 9); // Cove = Script Nonconnecting
+            put(3, 12); // Obtuse Cove = Script Broken Letter
+            put(4, 10); // Square Cove = Script Joining
+            put(5, 0); // Obtuse Square Cove = Sans Serif Square
+            put(6, 128); // Square = Serif
+            put(7, 2); // Thin = Serif Line
+            put(8, 7); // Bone = Rounded Bracket
+            put(9, 11); // Exeraggerated = Script Calligraphic
+            put(10, 3); // Triangle = Serif Triangle
+            put(11, 0); // Normal Sans = Sans Serif Square
+            put(12, 4); // Obtuse Sans = Serif Swath
+            put(13, 6); // Perp Sans = Serif Bracket
+            put(14, 8); // Flared = Flair Serif
+            put(15, 1); // Rounded = Sans Serif Round
+        }
+    };
+
+    private static final Map<Integer, Integer> FONT_WIDTH = new HashMap<Integer, Integer>() {
+        private static final long serialVersionUID = 1L;
+        {
+            /** The conversions between TTF and PCL are not 1 to 1 **/
+            put(1, -5); // 1 = Ultra Compressed
+            put(2, -4); // 2 = Extra Compressed
+            put(3, -3); // 3 = Compresses
+            put(4, -2); // 4 = Condensed
+            put(5, 0); // 5 = Normal
+            put(6, 2); // 6 = Expanded
+            put(7, 3); // 5 = Extra Expanded
+        }
+    };
+
+    private int scaleFactor = -1;
+    private PCLSymbolSet symbolSet = PCLSymbolSet.Bound_Generic;
+
+    public PCLTTFFontReader(Typeface font, PCLByteWriterUtil pclByteWriter) throws IOException {
+        super(font, pclByteWriter);
+        loadFont();
+    }
+
+    protected void loadFont() throws IOException {
+        if (typeface instanceof CustomFontMetricsMapper) {
+            CustomFontMetricsMapper fontMetrics = (CustomFontMetricsMapper) typeface;
+            CustomFont customFont = (CustomFont) fontMetrics.getRealFont();
+            fontStream = customFont.getInputStream();
+            reader = new FontFileReader(fontStream);
+
+            ttfFont = new TTFFile();
+            String header = OFFontLoader.readHeader(reader);
+            ttfFont.readFont(reader, header, customFont.getFullName());
+            readFontTables();
+        } else {
+            // TODO - Handle when typeface is not in the expected format for a PCL TrueType object
+        }
+    }
+
+    protected void readFontTables() throws IOException {
+        PCLTTFTable fontTable;
+        fontTable = readFontTable(OFTableName.PCLT);
+        if (fontTable instanceof PCLTTFPCLTFontTable) {
+            pcltTable = (PCLTTFPCLTFontTable) fontTable;
+        }
+        fontTable = readFontTable(OFTableName.OS2);
+        if (fontTable instanceof PCLTTFOS2FontTable) {
+            os2Table = (PCLTTFOS2FontTable) fontTable;
+        }
+        fontTable = readFontTable(OFTableName.POST);
+        if (fontTable instanceof PCLTTFPOSTFontTable) {
+            postTable = (PCLTTFPOSTFontTable) fontTable;
+        }
+    }
+
+    private PCLTTFTable readFontTable(OFTableName tableName) throws IOException {
+        if (ttfFont.seekTab(reader, tableName, 0)) {
+            return getTTFTableFactory().newInstance(tableName);
+        }
+        return null;
+    }
+
+    private PCLTTFTableFactory getTTFTableFactory() {
+        if (ttfTableFactory == null) {
+            ttfTableFactory = PCLTTFTableFactory.getInstance(reader);
+        }
+        return ttfTableFactory;
+    }
+
+    @Override
+    public int getDescriptorSize() {
+        return 72; // Descriptor size (leave at 72 for our purposes)
+    }
+
+    @Override
+    public int getHeaderFormat() {
+        return 15; // TrueType Scalable Font
+    }
+
+    @Override
+    public int getFontType() {
+        if (symbolSet == PCLSymbolSet.Unbound) {
+            return 11; // Font Type - Unbound TrueType Scalable font
+        } else {
+            return 2; // 0-255 (except 0, 7 and 27)
+        }
+    }
+
+    @Override
+    public int getStyleMSB() {
+        if (pcltTable != null) {
+            return getMSB(pcltTable.getStyle());
+        }
+        return 3;
+    }
+
+    @Override
+    public int getBaselinePosition() {
+        return 0; // Baseline position must be set to 0 for TTF fonts
+    }
+
+    @Override
+    public int getCellWidth() {
+        int[] bbox = ttfFont.getBBoxRaw();
+        return bbox[2] - bbox[0];
+    }
+
+    @Override
+    public int getCellHeight() {
+        int[] bbox = ttfFont.getBBoxRaw();
+        return bbox[3] - bbox[1];
+    }
+
+    @Override
+    public int getOrientation() {
+        return 0; // Scalable fonts (TrueType) must be 0
+    }
+
+    @Override
+    public int getSpacing() {
+        if (os2Table != null) {
+            return (os2Table.getPanose()[4] == 9) ? 0 : 1;
+        } else if (postTable != null) {
+            return postTable.getIsFixedPitch();
+        }
+        return 1;
+    }
+
+    @Override
+    public int getSymbolSet() {
+        if (pcltTable != null) {
+            return pcltTable.getSymbolSet();
+        } else {
+            return symbolSet.getKind1();
+        }
+    }
+
+    @Override
+    public int getPitch() {
+        int pitch = ttfFont.getCharWidthRaw(0x20);
+        if (pitch < 0) {
+            // No advance width found for the space character
+            return 0;
+        }
+        return pitch;
+    }
+
+    @Override
+    public int getHeight() {
+        return 0; // Fixed zero value for TrueType fonts
+    }
+
+    @Override
+    public int getXHeight() {
+        if (pcltTable != null) {
+            return pcltTable.getXHeight();
+        } else if (os2Table != null) {
+            return os2Table.getXHeight();
+        }
+        return 0;
+    }
+
+    @Override
+    public int getWidthType() {
+        if (pcltTable != null) {
+            return pcltTable.getWidthType();
+        } else if (os2Table != null) {
+            return convertTTFWidthClass(os2Table.getWidthClass());
+        }
+        return 0;
+    }
+
+    private int convertTTFWidthClass(int widthClass) {
+        if (FONT_WIDTH.containsKey(widthClass)) {
+            return FONT_WIDTH.get(widthClass);
+        } else {
+            return 0; // No match - return normal
+        }
+    }
+
+    @Override
+    public int getStyleLSB() {
+        if (pcltTable != null) {
+            return getLSB(pcltTable.getStyle());
+        }
+        return 224;
+    }
+
+    @Override
+    public int getStrokeWeight() {
+        if (pcltTable != null) {
+            return pcltTable.getStrokeWeight();
+        } else if (os2Table != null) {
+            return convertTTFWeightClass(os2Table.getWeightClass());
+        }
+        return 0;
+    }
+
+    private int convertTTFWeightClass(int weightClass) {
+        if (FONT_WEIGHT.containsKey(weightClass)) {
+            return FONT_WEIGHT.get(weightClass);
+        } else {
+            return 0; // No match - return normal
+        }
+    }
+
+    @Override
+    public int getTypefaceLSB() {
+        if (pcltTable != null) {
+            return getLSB(pcltTable.getTypeFamily());
+        }
+        return 254;
+    }
+
+    @Override
+    public int getTypefaceMSB() {
+        if (pcltTable != null) {
+            return getMSB(pcltTable.getTypeFamily());
+        }
+        return 0;
+    }
+
+    @Override
+    public int getSerifStyle() {
+        if (pcltTable != null) {
+            return pcltTable.getSerifStyle();
+        } else {
+            return convertFromTTFSerifStyle();
+        }
+    }
+
+    private int convertFromTTFSerifStyle() {
+        if (os2Table != null) {
+            int serifStyle = os2Table.getPanose()[1];
+            return FONT_SERIF.get(serifStyle);
+        }
+        return 0;
+    }
+
+    @Override
+    public int getQuality() {
+        return 2; // Letter quality
+    }
+
+    @Override
+    public int getPlacement() {
+        return 0; // Fixed value of 0 for TrueType (scalable fonts)
+    }
+
+    @Override
+    public int getUnderlinePosition() {
+        return 0; // Scalable fonts has a fixed value of 0 - See Master Underline Position
+    }
+
+    @Override
+    public int getUnderlineThickness() {
+        return 0; // Scalable fonts has a fixed value of 0 - See Master Underline Thickness
+    }
+
+    @Override
+    public int getTextHeight() {
+        return 2048;
+    }
+
+    @Override
+    public int getTextWidth() {
+        if (os2Table != null) {
+            return os2Table.getAvgCharWidth();
+        }
+        return 0;
+    }
+
+    @Override
+    public int getFirstCode() {
+        return 32;
+    }
+
+    @Override
+    public int getLastCode() {
+        return 255; // Bound font with a maximum of 255 characters
+    }
+
+    @Override
+    public int getPitchExtended() {
+        return 0; // Zero for Scalable fonts
+    }
+
+    @Override
+    public int getHeightExtended() {
+        return 0; // Zero for Scalable fonts
+    }
+
+    @Override
+    public int getCapHeight() {
+        if (pcltTable != null) {
+            return pcltTable.getStrokeWeight();
+        } else if (os2Table != null) {
+            return os2Table.getCapHeight();
+        }
+        return 0;
+    }
+
+    @Override
+    public int getFontNumber() {
+        if (pcltTable != null) {
+            return (int) pcltTable.getFontNumber();
+        }
+        return 0;
+    }
+
+    @Override
+    public String getFontName() {
+        if (pcltTable != null) {
+            return pcltTable.getTypeface();
+        } else {
+            return ttfFont.getFullName();
+        }
+    }
+
+    @Override
+    public int getScaleFactor() throws IOException {
+        if (scaleFactor == -1) {
+            OFTableName headTag = OFTableName.HEAD;
+            if (ttfFont.seekTab(reader, headTag, 0)) {
+                reader.readTTFLong(); // Version
+                reader.readTTFLong(); // Font revision
+                reader.readTTFLong(); // Check sum adjustment
+                reader.readTTFLong(); // Magic number
+                reader.readTTFShort(); // Flags
+                scaleFactor = reader.readTTFUShort(); // Units per em
+                return scaleFactor;
+            }
+        } else {
+            return scaleFactor;
+        }
+        return 0;
+    }
+
+    @Override
+    public int getMasterUnderlinePosition() throws IOException {
+        return (int) Math.round(getScaleFactor() * 0.2);
+    }
+
+    @Override
+    public int getMasterUnderlineThickness() throws IOException {
+        return (int) Math.round(getScaleFactor() * 0.05);
+    }
+
+    @Override
+    public int getFontScalingTechnology() {
+        return 1; // TrueType scalable font
+    }
+
+    @Override
+    public int getVariety() {
+        return 0; // TrueType fonts must be set to zero
+    }
+
+    public List<PCLFontSegment> getFontSegments() throws IOException {
+        List<PCLFontSegment> fontSegments = new ArrayList<PCLFontSegment>();
+        fontSegments.add(new PCLFontSegment(SegmentID.CC, getCharacterComplement()));
+        fontSegments.add(new PCLFontSegment(SegmentID.PA, pclByteWriter.toByteArray(os2Table.getPanose())));
+        fontSegments.add(new PCLFontSegment(SegmentID.GT, getGlobalTrueTypeData()));
+        fontSegments.add(new PCLFontSegment(SegmentID.CP, ttfFont.getCopyrightNotice().getBytes("US-ASCII")));
+        fontSegments.add(new PCLFontSegment(SegmentID.NULL, new byte[0]));
+        return fontSegments;
+    }
+
+    /**
+     * See Font Header Format 11-35 (Character Complement Array) in the PCL 5 Specification. Defined as an array of 8
+     * bytes specific to certain character sets. In this case specifying 0 for all values (default complement) means the
+     * font is compatible with any character sets. '110' on least significant bits signifies unicode. See specification
+     * for further customization.
+     */
+    private byte[] getCharacterComplement() {
+        byte[] ccUnicode = new byte[8];
+        ccUnicode[7] = 6;
+        return ccUnicode;
+    }
+
+    private byte[] getGlobalTrueTypeData() throws IOException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        Map<OFDirTabEntry, Integer> tableOffsets = new HashMap<OFDirTabEntry, Integer>();
+        // Version
+        baos.write(pclByteWriter.unsignedInt(1)); // Major
+        baos.write(pclByteWriter.unsignedInt(0)); // Minor
+        int numTables = 5; // head, hhea, hmtx, maxp and gdir
+        // Optional Hint Tables
+        OFDirTabEntry headTable = ttfFont.getDirectoryEntry(OFTableName.CVT);
+        if (headTable != null) {
+            numTables++;
+        }
+        OFDirTabEntry fpgmTable = ttfFont.getDirectoryEntry(OFTableName.FPGM);
+        if (fpgmTable != null) {
+            numTables++;
+        }
+        OFDirTabEntry prepTable = ttfFont.getDirectoryEntry(OFTableName.PREP);
+        if (prepTable != null) {
+            numTables++;
+        }
+        baos.write(pclByteWriter.unsignedInt(numTables)); // numTables
+        int maxPowerNumTables = pclByteWriter.maxPower2(numTables);
+        int searchRange = maxPowerNumTables * 16;
+        baos.write(pclByteWriter.unsignedInt(searchRange));
+        baos.write(pclByteWriter.unsignedInt(pclByteWriter.log(maxPowerNumTables, 2))); // Entry Selector
+        baos.write(pclByteWriter.unsignedInt(numTables * 16 - searchRange)); // Range shift
+
+        // Add default data tables
+        writeTrueTypeTable(baos, OFTableName.HEAD, tableOffsets);
+        writeTrueTypeTable(baos, OFTableName.HHEA, tableOffsets);
+        writeTrueTypeTable(baos, OFTableName.HMTX, tableOffsets);
+        writeTrueTypeTable(baos, OFTableName.MAXP, tableOffsets);
+
+        // Write the blank GDIR directory which is built in memory on the printer
+        writeGDIR(baos);
+
+        // Add optional data tables (for hints)
+        writeTrueTypeTable(baos, OFTableName.CVT, tableOffsets);
+        writeTrueTypeTable(baos, OFTableName.FPGM, tableOffsets);
+        writeTrueTypeTable(baos, OFTableName.PREP, tableOffsets);
+
+        baos = copyTables(tableOffsets, baos);
+
+        return baos.toByteArray();
+    }
+
+    private void writeTrueTypeTable(ByteArrayOutputStream baos, OFTableName table,
+            Map<OFDirTabEntry, Integer> tableOffsets) throws IOException, UnsupportedEncodingException {
+        OFDirTabEntry tabEntry = ttfFont.getDirectoryEntry(table);
+        if (tabEntry != null) {
+            baos.write(tabEntry.getTag());
+            baos.write(pclByteWriter.unsignedLongInt(tabEntry.getChecksum()));
+            tableOffsets.put(tabEntry, baos.size());
+            baos.write(pclByteWriter.unsignedLongInt(0)); // Offset to be set later
+            long length = (tabEntry.getLength() > HMTX_RESTRICT_SIZE)
+                    ? HMTX_RESTRICT_SIZE
+                    : tabEntry.getLength();
+            baos.write(pclByteWriter.unsignedLongInt(length));
+        }
+    }
+
+    private void writeGDIR(ByteArrayOutputStream baos) throws UnsupportedEncodingException, IOException {
+        baos.write("gdir".getBytes("ISO-8859-1"));
+        baos.write(pclByteWriter.unsignedLongInt(0)); // Checksum
+        baos.write(pclByteWriter.unsignedLongInt(0)); // Offset
+        baos.write(pclByteWriter.unsignedLongInt(0)); // Length
+    }
+
+    private ByteArrayOutputStream copyTables(Map<OFDirTabEntry, Integer> tableOffsets, ByteArrayOutputStream baos)
+            throws IOException {
+        Map<Integer, byte[]> offsetValues = new HashMap<Integer, byte[]>();
+        //for (OFDirTabEntry table : tableOffsets.keySet()) {
+        for (Entry<OFDirTabEntry, Integer> table : tableOffsets.entrySet()) {
+            byte[] tableData = reader.getBytes((int) table.getKey().getOffset(), (int) table.getKey().getLength());
+            if (tableData.length > HMTX_RESTRICT_SIZE) {
+                byte[] truncated = new byte[HMTX_RESTRICT_SIZE];
+                System.arraycopy(tableData, 0, truncated, 0, HMTX_RESTRICT_SIZE);
+                tableData = truncated;
+            }
+            // Update the offset in the table directory
+            offsetValues.put(table.getValue(), pclByteWriter.unsignedLongInt(baos.size()));
+            // Write the table data to the end of the TrueType segment output
+            baos.write(tableData);
+        }
+        baos = updateOffsets(baos, offsetValues);
+        return baos;
+    }
+
+    private ByteArrayOutputStream updateOffsets(ByteArrayOutputStream baos, Map<Integer, byte[]> offsets)
+            throws IOException {
+        byte[] softFont = baos.toByteArray();
+        for (int offset : offsets.keySet()) {
+            pclByteWriter.updateDataAtLocation(softFont, offsets.get(offset), offset);
+        }
+        baos = new ByteArrayOutputStream();
+        baos.write(softFont);
+        return baos;
+    }
+
+    @Override
+    public Map<Integer, int[]> getCharacterOffsets() throws IOException {
+        List<OFMtxEntry> mtx = ttfFont.getMtx();
+        OFTableName glyfTag = OFTableName.GLYF;
+        Map<Integer, int[]> charOffsets = new HashMap<Integer, int[]>();
+        OFDirTabEntry tabEntry = ttfFont.getDirectoryEntry(glyfTag);
+        if (ttfFont.seekTab(reader, glyfTag, 0)) {
+            for (int i = 1; i < mtx.size(); i++) {
+                OFMtxEntry entry = mtx.get(i);
+                OFMtxEntry nextEntry;
+                int nextOffset = 0;
+                int charCode = 0;
+                if (entry.getUnicodeIndex().size() > 0) {
+                    charCode = (Integer) entry.getUnicodeIndex().get(0);
+                } else {
+                    charCode = entry.getIndex();
+                }
+
+                if (i < mtx.size() - 1) {
+                    nextEntry = mtx.get(i + 1);
+                    nextOffset = (int) nextEntry.getOffset();
+                } else {
+                    nextOffset = (int) ttfFont.getLastGlyfLocation();
+                }
+                int glyphOffset = (int) entry.getOffset();
+                int glyphLength = nextOffset - glyphOffset;
+
+                charOffsets.put(charCode, new int[]{(int) tabEntry.getOffset() + glyphOffset, glyphLength});
+            }
+        }
+        return charOffsets;
+    }
+
+    @Override
+    public OpenFont getFontFile() {
+        return ttfFont;
+    }
+
+    @Override
+    public FontFileReader getFontFileReader() {
+        return reader;
+    }
+}
diff --git a/src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFOS2FontTable.java b/src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFOS2FontTable.java
new file mode 100644 (file)
index 0000000..4c5f98d
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * 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.pcl.fonts.truetype;
+
+import java.io.IOException;
+
+import org.apache.fop.fonts.truetype.FontFileReader;
+
+public class PCLTTFOS2FontTable extends PCLTTFTable {
+    private int avgCharWidth;
+    private int xHeight;
+    private int widthClass;
+    private int weightClass;
+    private int capHeight;
+    private int[] panose = new int[10];
+
+    public PCLTTFOS2FontTable(FontFileReader in) throws IOException {
+        super(in);
+        int version = reader.readTTFUShort(); // Version
+        avgCharWidth = reader.readTTFShort();
+        weightClass = reader.readTTFShort();
+        widthClass = reader.readTTFShort();
+        skipShort(reader, 12);
+        for (int i = 0; i < 10; i++) {
+            panose[i] = reader.readTTFByte();
+        }
+        skipLong(reader, 4);
+        skipByte(reader, 4);
+        skipShort(reader, 8);
+        if (version >= 2) {
+            skipLong(reader, 2);
+            xHeight = reader.readTTFShort();
+            capHeight = reader.readTTFShort();
+        }
+    }
+
+    public int getAvgCharWidth() {
+        return avgCharWidth;
+    }
+
+    public int getXHeight() {
+        return xHeight;
+    }
+
+    public int getWidthClass() {
+        return widthClass;
+    }
+
+    public int getWeightClass() {
+        return weightClass;
+    }
+
+    public int getCapHeight() {
+        return capHeight;
+    }
+
+    public int[] getPanose() {
+        return panose;
+    }
+}
diff --git a/src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFPCLTFontTable.java b/src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFPCLTFontTable.java
new file mode 100644 (file)
index 0000000..5be46a4
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * 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.pcl.fonts.truetype;
+
+import java.io.IOException;
+
+import org.apache.fop.fonts.truetype.FontFileReader;
+
+public class PCLTTFPCLTFontTable extends PCLTTFTable {
+    private long version;
+    private long fontNumber;
+    private int pitch;
+    private int xHeight;
+    private int style;
+    private int typeFamily;
+    private int capHeight;
+    private int symbolSet;
+    private String typeface;
+    private String characterComplement;
+    private String filename;
+    private int strokeWeight;
+    private int widthType;
+    private int serifStyle;
+
+    public PCLTTFPCLTFontTable(FontFileReader in) throws IOException {
+        super(in);
+        version = reader.readTTFULong();
+        fontNumber = reader.readTTFULong();
+        pitch = reader.readTTFUShort();
+        xHeight = reader.readTTFUShort();
+        style = reader.readTTFUShort();
+        typeFamily = reader.readTTFUShort();
+        capHeight = reader.readTTFUShort();
+        symbolSet = reader.readTTFUShort();
+        typeface = reader.readTTFString(16);
+        characterComplement = reader.readTTFString(8);
+        filename = reader.readTTFString(6);
+        strokeWeight = reader.readTTFUShort();
+        widthType = reader.readTTFUShort();
+        serifStyle = reader.readTTFUByte();
+    }
+
+    public long getVersion() {
+        return version;
+    }
+
+    public long getFontNumber() {
+        return fontNumber;
+    }
+
+    public int getPitch() {
+        return pitch;
+    }
+
+    public int getXHeight() {
+        return xHeight;
+    }
+
+    public int getStyle() {
+        return style;
+    }
+
+    public int getTypeFamily() {
+        return typeFamily;
+    }
+
+    public int getCapHeight() {
+        return capHeight;
+    }
+
+    public int getSymbolSet() {
+        return symbolSet;
+    }
+
+    public String getTypeface() {
+        return typeface;
+    }
+
+    public String getCharacterComplement() {
+        return characterComplement;
+    }
+
+    public String getFilename() {
+        return filename;
+    }
+
+    public int getStrokeWeight() {
+        return strokeWeight;
+    }
+
+    public int getWidthType() {
+        return widthType;
+    }
+
+    public int getSerifStyle() {
+        return serifStyle;
+    }
+}
diff --git a/src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFPOSTFontTable.java b/src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFPOSTFontTable.java
new file mode 100644 (file)
index 0000000..9995f7f
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * 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.pcl.fonts.truetype;
+
+import java.io.IOException;
+
+import org.apache.fop.fonts.truetype.FontFileReader;
+
+public class PCLTTFPOSTFontTable extends PCLTTFTable {
+    private int underlinePosition;
+    private int underlineThickness;
+    private int isFixedPitch;
+
+    public PCLTTFPOSTFontTable(FontFileReader in) throws IOException {
+        super(in);
+        reader.readTTFLong(); // Version
+        reader.readTTFLong(); // Italic Angle
+        underlinePosition = reader.readTTFShort();
+        underlineThickness = reader.readTTFShort();
+        isFixedPitch = (int) reader.readTTFULong();
+    }
+
+    public int getUnderlinePosition() {
+        return underlinePosition;
+    }
+
+    public int getUnderlineThickness() {
+        return underlineThickness;
+    }
+
+    public int getIsFixedPitch() {
+        return isFixedPitch;
+    }
+}
diff --git a/src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFTable.java b/src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFTable.java
new file mode 100644 (file)
index 0000000..cfe8664
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * 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.pcl.fonts.truetype;
+
+import java.io.IOException;
+
+import org.apache.fop.fonts.truetype.FontFileReader;
+
+public class PCLTTFTable {
+    protected FontFileReader reader;
+
+    public PCLTTFTable(FontFileReader reader) {
+        this.reader = reader;
+    }
+
+    protected void skipShort(FontFileReader reader, int skips)
+            throws IOException {
+        reader.skip(skips * 2);
+    }
+
+    protected void skipLong(FontFileReader reader, int skips)
+            throws IOException {
+        reader.skip(skips * 4);
+    }
+
+    protected void skipByte(FontFileReader reader, int skips)
+            throws IOException {
+        reader.skip(skips);
+    }
+}
diff --git a/src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFTableFactory.java b/src/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFTableFactory.java
new file mode 100644 (file)
index 0000000..e72563e
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * 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.pcl.fonts.truetype;
+
+import java.io.IOException;
+
+import org.apache.fop.fonts.truetype.FontFileReader;
+import org.apache.fop.fonts.truetype.OFTableName;
+
+public final class PCLTTFTableFactory {
+    private FontFileReader reader;
+
+    private PCLTTFTableFactory(FontFileReader reader) {
+        this.reader = reader;
+    }
+
+    public static PCLTTFTableFactory getInstance(FontFileReader reader) {
+        return new PCLTTFTableFactory(reader);
+    }
+
+    public PCLTTFTable newInstance(OFTableName tableName)
+            throws IOException {
+        if (tableName == OFTableName.PCLT) {
+            return new PCLTTFPCLTFontTable(reader);
+        } else if (tableName == OFTableName.OS2) {
+            return new PCLTTFOS2FontTable(reader);
+        } else if (tableName == OFTableName.POST) {
+            return new PCLTTFPOSTFontTable(reader);
+        }
+        return null;
+    }
+}
diff --git a/test/java/org/apache/fop/render/pcl/fonts/MockPCLTTFFontReader.java b/test/java/org/apache/fop/render/pcl/fonts/MockPCLTTFFontReader.java
new file mode 100644 (file)
index 0000000..a155dd4
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * 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.pcl.fonts;
+
+import java.io.IOException;
+
+import org.apache.fop.fonts.CustomFont;
+import org.apache.fop.fonts.Typeface;
+import org.apache.fop.fonts.truetype.FontFileReader;
+import org.apache.fop.fonts.truetype.TTFFile;
+import org.apache.fop.render.java2d.CustomFontMetricsMapper;
+import org.apache.fop.render.pcl.fonts.truetype.PCLTTFFontReader;
+
+public class MockPCLTTFFontReader extends PCLTTFFontReader {
+
+    public MockPCLTTFFontReader(Typeface font, PCLByteWriterUtil pclByteWriter) throws IOException {
+        super(font, pclByteWriter);
+    }
+
+    @Override
+    protected void loadFont() throws IOException {
+        if (typeface instanceof CustomFontMetricsMapper) {
+            CustomFontMetricsMapper fontMetrics = (CustomFontMetricsMapper) typeface;
+            CustomFont customFont = (CustomFont) fontMetrics.getRealFont();
+            fontStream = customFont.getInputStream();
+            reader = new FontFileReader(fontStream);
+
+            ttfFont = new TTFFile();
+            ttfFont.readFont(reader, customFont.getFullName());
+            readFontTables();
+        } else {
+            // TODO - Handle when typeface is not in the expected format for a PCL TrueType object
+        }
+    }
+}
diff --git a/test/java/org/apache/fop/render/pcl/fonts/PCLByteWriterUtilTestCase.java b/test/java/org/apache/fop/render/pcl/fonts/PCLByteWriterUtilTestCase.java
new file mode 100644 (file)
index 0000000..a21f204
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * 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.pcl.fonts;
+
+import java.io.IOException;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertArrayEquals;
+
+public class PCLByteWriterUtilTestCase {
+    private PCLByteWriterUtil byteWriter;
+
+    @Before
+    public void setUp() {
+        byteWriter = new PCLByteWriterUtil();
+    }
+
+    @Test
+    public void testWriteMethods() throws IOException {
+        byte[] output = byteWriter.writeCommand("(s4X");
+        // 27 = PCL escape character with rest in ASCII format
+        byte[] command = {27, 40, 115, 52, 88};
+        assertArrayEquals(command, output);
+
+        byte[] resultB = byteWriter.unsignedLongInt(102494);
+        byte[] compareB = {0, 1, -112, 94};
+        assertArrayEquals(compareB, resultB);
+
+        byte[] resultC = byteWriter.unsignedInt(1024);
+        byte[] compareC = {4, 0};
+        assertArrayEquals(compareC, resultC);
+    }
+
+    @Test
+    public void testUtilMethods() throws IOException {
+        byte[] anArray = {1, 2, 3, 4, 5, 9, 10};
+        byte[] insertArray = {6, 7, 8};
+        byte[] result = byteWriter.insertIntoArray(5, anArray, insertArray);
+        byte[] compareA = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+        assertArrayEquals(compareA, result);
+
+        byte[] reverse = {10, 9, 8, 7, 6};
+        byteWriter.updateDataAtLocation(compareA, reverse, 5);
+        byte[] compareB = {1, 2, 3, 4, 5, 10, 9, 8, 7, 6};
+        assertArrayEquals(compareB, compareA);
+
+        byte[] anArrayC = {1, 2, 3, 4, 5};
+        byte[] resultC = byteWriter.padBytes(anArrayC, 10);
+        byte[] compareC = {1, 2, 3, 4, 5, 0, 0, 0, 0, 0};
+        assertArrayEquals(compareC, resultC);
+
+        byte[] resultD = byteWriter.padBytes(anArrayC, 10, 1);
+        byte[] compareD = {1, 2, 3, 4, 5, 1, 1, 1, 1, 1};
+        assertArrayEquals(compareD, resultD);
+    }
+}
diff --git a/test/java/org/apache/fop/render/pcl/fonts/PCLFontReaderFactoryTestCase.java b/test/java/org/apache/fop/render/pcl/fonts/PCLFontReaderFactoryTestCase.java
new file mode 100644 (file)
index 0000000..2860afb
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * 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.pcl.fonts;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.net.URI;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.apache.fop.fonts.CustomFont;
+import org.apache.fop.fonts.FontType;
+import org.apache.fop.render.java2d.CustomFontMetricsMapper;
+import org.apache.fop.render.pcl.fonts.truetype.PCLTTFFontReader;
+
+public class PCLFontReaderFactoryTestCase {
+    private static final String TEST_FONT_TTF = "./test/resources/fonts/ttf/DejaVuLGCSerif.ttf";
+
+    @Test
+    public void verifyTypeIdentification() throws Exception {
+        CustomFont sbFont = mock(CustomFont.class);
+        when(sbFont.getInputStream()).thenReturn(new FileInputStream(new File(TEST_FONT_TTF)));
+        when(sbFont.getEmbedFileURI()).thenReturn(new URI(TEST_FONT_TTF));
+        CustomFontMetricsMapper customFont = new CustomFontMetricsMapper(sbFont);
+        when(customFont.getFontType()).thenReturn(FontType.TRUETYPE);
+        // Have to mock the input stream twice otherwise get a Stream is closed exception
+        when(((CustomFont) customFont.getRealFont()).getInputStream()).thenReturn(
+                new FileInputStream(new File(TEST_FONT_TTF)));
+        PCLFontReaderFactory fontReaderFactory = PCLFontReaderFactory.getInstance(null);
+        assertTrue(fontReaderFactory.createInstance(customFont) instanceof PCLTTFFontReader);
+    }
+}
diff --git a/test/java/org/apache/fop/render/pcl/fonts/PCLTTFFontReaderTestCase.java b/test/java/org/apache/fop/render/pcl/fonts/PCLTTFFontReaderTestCase.java
new file mode 100644 (file)
index 0000000..88c613e
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * 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.pcl.fonts;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.apache.fop.fonts.CustomFont;
+import org.apache.fop.render.java2d.CustomFontMetricsMapper;
+import org.apache.fop.render.pcl.fonts.PCLFontSegment.SegmentID;
+import org.apache.fop.render.pcl.fonts.truetype.PCLTTFFontReader;
+
+public class PCLTTFFontReaderTestCase {
+
+    private CustomFontMetricsMapper customFont = mock(CustomFontMetricsMapper.class);
+    private PCLByteWriterUtil byteWriter;
+    private static final String TEST_FONT_A = "./test/resources/fonts/ttf/DejaVuLGCSerif.ttf";
+
+    @Before
+    public void setUp() {
+        byteWriter = new PCLByteWriterUtil();
+    }
+
+    @Test
+    public void verifyFontAData() throws Exception {
+        CustomFont sbFont = mock(CustomFont.class);
+        when(sbFont.getInputStream()).thenReturn(new FileInputStream(new File(TEST_FONT_A)));
+        when(customFont.getRealFont()).thenReturn(sbFont);
+        PCLTTFFontReader reader = new MockPCLTTFFontReader(customFont, byteWriter);
+        verifyFontData(reader);
+        validateOffsets(reader);
+        validateFontSegments(reader);
+    }
+
+    /**
+     * Compares the input font data against a sample of the data read and calculated by the reader. The assertions are
+     * made against data taken from the TrueType Font Analyzer tool.
+     * @param reader The reader
+     */
+    private void verifyFontData(PCLTTFFontReader reader) {
+        assertEquals(reader.getCellWidth(), 5015); // Bounding box X2 - X1
+        assertEquals(reader.getCellHeight(), 3254); // Bounding box Y2 - Y1
+        assertEquals(reader.getCapHeight(), 0); // OS2Table.capHeight
+        assertEquals(reader.getFontName(), "DejaVu LGC Serif"); // Full name read by TTFFont object
+        assertEquals(reader.getFirstCode(), 32); // Always 32 for bound font
+        assertEquals(reader.getLastCode(), 255); // Always 255 for bound font
+
+        // Values that require conversion tables (See PCLTTFFontReader.java)
+        assertEquals(reader.getStrokeWeight(), 0); // Weight Class 400 (regular) should be equivalent 0
+        assertEquals(reader.getSerifStyle(), 128); // Serif Style 0 should equal 0
+        assertEquals(reader.getWidthType(), 0); // Width Class 5 (regular) should be equivalent 0
+    }
+
+    private void validateOffsets(PCLTTFFontReader reader) throws IOException {
+        // Offsets are stored with their character ID with the array [offset, length]
+        Map<Integer, int[]> offsets = reader.getCharacterOffsets();
+
+        // Test data
+        int[] charC = {27644, 144}; // Char index = 99
+        int[] charDollar = {16044, 264}; // Char index = 36
+        int[] charOne = {17808, 176}; // Char index = 49
+        int[] charUpperD = {21236, 148}; // Char index = 68
+        int[] charUpperJ = {22140, 176}; // Char index = 74
+
+        assertArrayEquals(offsets.get(99), charC);
+        assertArrayEquals(offsets.get(36), charDollar);
+        assertArrayEquals(offsets.get(49), charOne);
+        assertArrayEquals(offsets.get(68), charUpperD);
+        assertArrayEquals(offsets.get(74), charUpperJ);
+    }
+
+    /**
+     * Verifies the font segment data copied originally from the TrueType font. Data was verified using TrueType Font
+     * Analyzer and PCLParaphernalia tool.
+     * @param reader The reader
+     * @throws IOException
+     */
+    private void validateFontSegments(PCLTTFFontReader reader) throws IOException {
+        List<PCLFontSegment> segments = reader.getFontSegments();
+        assertEquals(segments.size(), 5);
+        for (PCLFontSegment segment : segments) {
+            if (segment.getIdentifier() == SegmentID.PA) {
+                // Panose
+                assertEquals(segment.getData().length, 10);
+                byte[] panose = {2, 6, 6, 3, 5, 6, 5, 2, 2, 4};
+                assertArrayEquals(segment.getData(), panose);
+            } else if (segment.getIdentifier() == SegmentID.NULL) {
+                // Terminating segment
+                assertEquals(segment.getData().length, 0);
+            }
+        }
+    }
+}
diff --git a/test/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFCharacterWriterTestCase.java b/test/java/org/apache/fop/render/pcl/fonts/truetype/PCLTTFCharacterWriterTestCase.java
new file mode 100644 (file)
index 0000000..10263d0
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * 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.pcl.fonts.truetype;
+
+import java.io.File;
+import java.io.FileInputStream;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.apache.fop.fonts.CustomFont;
+import org.apache.fop.fonts.truetype.FontFileReader;
+import org.apache.fop.fonts.truetype.OFFontLoader;
+import org.apache.fop.fonts.truetype.TTFFile;
+import org.apache.fop.render.java2d.CustomFontMetricsMapper;
+import org.apache.fop.render.pcl.fonts.PCLByteWriterUtil;
+import org.apache.fop.render.pcl.fonts.PCLSoftFont;
+
+public class PCLTTFCharacterWriterTestCase {
+
+    private PCLTTFCharacterWriter characterWriter;
+    private PCLSoftFont softFont;
+    private CustomFontMetricsMapper customFont = mock(CustomFontMetricsMapper.class);
+    private static final String TEST_FONT_A = "./test/resources/fonts/ttf/DejaVuLGCSerif.ttf";
+
+    @Test
+    public void verifyCharacterDefinition() throws Exception {
+        CustomFont sbFont = mock(CustomFont.class);
+        when(customFont.getRealFont()).thenReturn(sbFont);
+        softFont = new PCLSoftFont(1, customFont);
+        TTFFile openFont = new TTFFile();
+        FontFileReader reader = new FontFileReader(new FileInputStream(new File(TEST_FONT_A)));
+        String header = OFFontLoader.readHeader(reader);
+        openFont.readFont(reader, header);
+        softFont.setOpenFont(openFont);
+        softFont.setReader(reader);
+
+        characterWriter = new PCLTTFCharacterWriter(softFont);
+        byte[] charDefinition = characterWriter.writeCharacterDefinitions("f");
+        PCLByteWriterUtil pclByteWriter = new PCLByteWriterUtil();
+        // Character command
+        byte[] command = pclByteWriter.writeCommand(String.format("*c%dE", 32));
+        assertArrayEquals(getBytes(charDefinition, 0, 6), command);
+        // Character definition command
+        byte[] charDefCommand = pclByteWriter.writeCommand(String.format("(s%dW", 210));
+        assertArrayEquals(getBytes(charDefinition, 6, 7), charDefCommand);
+    }
+
+    private byte[] getBytes(byte[] byteArray, int offset, int length) {
+        byte[] result = new byte[length];
+        int count = 0;
+        for (int i = offset; i < offset + length; i++) {
+            result[count++] = byteArray[i];
+        }
+        return result;
+    }
+}