]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
Changed the way AFP PTOCA TransparentData control sequences are written so that they...
authorMehdi Houshmand <mehdi@apache.org>
Fri, 11 May 2012 13:14:17 +0000 (13:14 +0000)
committerMehdi Houshmand <mehdi@apache.org>
Fri, 11 May 2012 13:14:17 +0000 (13:14 +0000)
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1337142 13f79535-47bb-0310-9956-ffa450edef68

src/java/org/apache/fop/afp/fonts/CharactersetEncoder.java
src/java/org/apache/fop/afp/modca/AxisOrientation.java [new file with mode: 0644]
src/java/org/apache/fop/afp/modca/IncludeObject.java
src/java/org/apache/fop/afp/modca/Rotation.java [new file with mode: 0644]
src/java/org/apache/fop/afp/ptoca/PtocaBuilder.java
src/java/org/apache/fop/afp/ptoca/PtocaConstants.java
src/java/org/apache/fop/afp/ptoca/TransparentDataControlSequence.java [new file with mode: 0644]
test/java/org/apache/fop/afp/ptoca/TransparentDataControlSequenceTestCase.java [new file with mode: 0644]

index 229123a82ee7ad606503c52c8a9f157de50618eb..6d85c0f5225e8d7f042b14296d73e539c6ce7639 100644 (file)
@@ -105,9 +105,9 @@ public abstract class CharactersetEncoder {
         @Override
         EncodedChars getEncodedChars(byte[] byteArray, int length) {
             if (byteArray[0] == 0x0E && byteArray[length - 1] == 0x0F) {
-                return new EncodedChars(byteArray, 1, length - 2);
+                return new EncodedChars(byteArray, 1, length - 2, true);
             }
-            return new EncodedChars(byteArray);
+            return new EncodedChars(byteArray, true);
         }
     }
 
@@ -123,7 +123,7 @@ public abstract class CharactersetEncoder {
 
         @Override
         EncodedChars getEncodedChars(byte[] byteArray, int length) {
-            return new EncodedChars(byteArray);
+            return new EncodedChars(byteArray, false);
         }
     }
 
@@ -145,36 +145,25 @@ public abstract class CharactersetEncoder {
     /**
      * A container for encoded character bytes
      */
-    public static final class EncodedChars {
+    public static class EncodedChars {
 
         private final byte[] bytes;
-
         private final int offset;
-
         private final int length;
+        private final boolean isDBCS;
 
-        private EncodedChars(byte[] bytes, int offset, int length) {
-            if (offset < 0) {
-                throw new IllegalArgumentException();
-            }
-
-            if (length < 0) {
-                throw new IllegalArgumentException();
-            }
-
-            if (offset + length > bytes.length) {
+        private EncodedChars(byte[] bytes, int offset, int length, boolean isDBCS) {
+            if (offset < 0 || length < 0 || offset + length > bytes.length) {
                 throw new IllegalArgumentException();
             }
-
             this.bytes = bytes;
-
             this.offset = offset;
-
             this.length = length;
+            this.isDBCS = isDBCS;
         }
 
-        private EncodedChars(byte[] bytes) {
-           this(bytes, 0, bytes.length);
+        private EncodedChars(byte[] bytes, boolean isDBCS) {
+            this(bytes, 0, bytes.length, isDBCS);
         }
 
         /**
@@ -186,18 +175,9 @@ public abstract class CharactersetEncoder {
          * @throws IOException if an I/O error occurs
          */
         public void writeTo(OutputStream out, int offset, int length) throws IOException {
-            if (offset < 0) {
+            if (offset < 0 || length < 0 || offset + length > bytes.length) {
                 throw new IllegalArgumentException();
             }
-
-            if (length < 0) {
-                throw new IllegalArgumentException();
-            }
-
-            if (offset + length > this.length) {
-                throw new IllegalArgumentException();
-            }
-
             out.write(bytes, this.offset + offset, length);
         }
 
@@ -210,6 +190,15 @@ public abstract class CharactersetEncoder {
             return length;
         }
 
+        /**
+         * Indicates whether or not the EncodedChars object wraps double byte characters.
+         *
+         * @return true if the wrapped characters are double byte (DBCSs)
+         */
+        public boolean isDBCS() {
+            return isDBCS;
+        }
+
         /**
          * The bytes
          *
diff --git a/src/java/org/apache/fop/afp/modca/AxisOrientation.java b/src/java/org/apache/fop/afp/modca/AxisOrientation.java
new file mode 100644 (file)
index 0000000..a017fe5
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * 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.afp.modca;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Represents the 4 bytes that specify the axis-area rotation reference coordinate system
+ */
+public enum AxisOrientation {
+
+    RIGHT_HANDED_0(Rotation.ROTATION_0, Rotation.ROTATION_90),
+    RIGHT_HANDED_90(Rotation.ROTATION_90, Rotation.ROTATION_180),
+    RIGHT_HANDED_180(Rotation.ROTATION_180, Rotation.ROTATION_270),
+    RIGHT_HANDED_270(Rotation.ROTATION_270, Rotation.ROTATION_0);
+
+    /**
+     * The object area's X-axis rotation from the X axis of the reference coordinate system
+     */
+    private final Rotation xoaOrent;
+    /**
+     * The object area's Y-axis rotation from the Y axis of the reference coordinate system
+     */
+    private final Rotation yoaOrent;
+
+    public void writeTo(byte[] out, int offset) {
+        xoaOrent.writeTo(out, offset);
+        yoaOrent.writeTo(out, offset + 2);
+    }
+
+    private AxisOrientation(Rotation xoaOrent, Rotation yoaOrent) {
+        this.xoaOrent = xoaOrent;
+        this.yoaOrent = yoaOrent;
+    }
+
+    /**
+     * Writes the axis orientation area bytes to the output stream.
+     *
+     * @param stream the output stream to write to
+     * @throws IOException if an I/O error occurs
+     */
+    public void writeTo(OutputStream stream) throws IOException {
+        byte[] data = new byte[4];
+        writeTo(data, 0);
+        stream.write(data);
+    }
+
+    /**
+     * Gets the right-handed axis orientation object for a given orientation in degrees.
+     *
+     * @param orientation the orientation in degrees
+     * @return the {@link AxisOrientation} object
+     */
+    public static AxisOrientation getRightHandedAxisOrientationFor(int orientation) {
+        switch (orientation) {
+            case 0: return RIGHT_HANDED_0;
+            case 90: return RIGHT_HANDED_90;
+            case 180: return RIGHT_HANDED_180;
+            case 270: return RIGHT_HANDED_270;
+            default: throw new IllegalArgumentException(
+            "The orientation must be one of the values 0, 90, 180, 270");
+        }
+    }
+}
index 68fa72688048f0e3672baddc7587e12b7a04ee83..4dca1b3573e0670bb176a3488885d8a4ce59c5b0 100644 (file)
@@ -67,7 +67,7 @@ public class IncludeObject extends AbstractNamedAFPObject {
     private int yoaOset = 0;
 
     /** the orientation of the referenced object */
-    private ObjectAreaRotation oaOrent = ObjectAreaRotation.RIGHT_HANDED_0;
+    private AxisOrientation oaOrent = AxisOrientation.RIGHT_HANDED_0;
 
     /** the X-axis origin defined in the object */
     private int xocaOset = -1;
@@ -93,7 +93,7 @@ public class IncludeObject extends AbstractNamedAFPObject {
      *            The orientation (0,90, 180, 270)
      */
     public void setObjectAreaOrientation(int orientation) {
-        this.oaOrent = ObjectAreaRotation.objectAreaRotationFor(orientation);
+        this.oaOrent = AxisOrientation.getRightHandedAxisOrientationFor(orientation);
     }
 
     /**
@@ -234,69 +234,4 @@ public class IncludeObject extends AbstractNamedAFPObject {
         addTriplet(new MeasurementUnitsTriplet(xRes, xRes));
     }
 
-    /**
-     * Represents the 4 bytes that specify the area rotation reference coordinate system
-     *
-     */
-    private enum ObjectAreaRotation {
-
-        RIGHT_HANDED_0(Rotation.ROTATION_0, Rotation.ROTATION_90),
-        RIGHT_HANDED_90(Rotation.ROTATION_90, Rotation.ROTATION_180),
-        RIGHT_HANDED_180(Rotation.ROTATION_180, Rotation.ROTATION_270),
-        RIGHT_HANDED_270(Rotation.ROTATION_270, Rotation.ROTATION_0);
-
-        /**
-         * The object area's X-axis rotation from the X axis of the reference coordinate system
-         */
-        private final Rotation xoaOrent;
-        /**
-         * The object area's Y-axis rotation from the Y axis of the reference coordinate system
-         */
-        private final Rotation yoaOrent;
-
-        public void writeTo(byte[] out, int offset) {
-            xoaOrent.writeTo(out, offset);
-            yoaOrent.writeTo(out, offset + 2);
-        }
-
-        ObjectAreaRotation(Rotation xoaOrent, Rotation yoaOrent) {
-            this.xoaOrent = xoaOrent;
-            this.yoaOrent = yoaOrent;
-        }
-
-        private static ObjectAreaRotation objectAreaRotationFor(int orientation) {
-            switch (orientation) {
-                case 0: return RIGHT_HANDED_0;
-                case 90: return RIGHT_HANDED_90;
-                case 180: return RIGHT_HANDED_180;
-                case 270: return RIGHT_HANDED_270;
-                default: throw new IllegalArgumentException(
-                "The orientation must be one of the values 0, 90, 180, 270");
-            }
-        }
-    }
-
-    /**
-     * Represents a rotation value
-     *
-     */
-    private enum Rotation {
-
-        ROTATION_0(0),
-        ROTATION_90(0x2D),
-        ROTATION_180(0x5A),
-        ROTATION_270(0x87);
-
-        private final byte firstByte;
-
-        public void writeTo(byte[] out, int offset) {
-            out[offset] = firstByte;
-            out[offset + 1] = (byte)0;
-        }
-
-        Rotation(int firstByte) {
-            this.firstByte = (byte) firstByte;
-        }
-    }
-
 }
diff --git a/src/java/org/apache/fop/afp/modca/Rotation.java b/src/java/org/apache/fop/afp/modca/Rotation.java
new file mode 100644 (file)
index 0000000..a307e1c
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * 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.afp.modca;
+
+/**
+ * Represents a rotation value
+ *
+ */
+public enum Rotation {
+    ROTATION_0(0),
+    ROTATION_90(0x2D),
+    ROTATION_180(0x5A),
+    ROTATION_270(0x87);
+
+    private final byte firstByte;
+
+    public void writeTo(byte[] out, int offset) {
+        out[offset] = firstByte;
+        out[offset + 1] = (byte)0;
+    }
+
+    private Rotation(int firstByte) {
+        this.firstByte = (byte) firstByte;
+    }
+
+    public byte getByte() {
+        return firstByte;
+    }
+}
index 1ea63c7f9139099795c27187ff2a47f26536a621..befd2cc1ab6f4e12893dc67c12836066ef03593a 100644 (file)
@@ -31,6 +31,8 @@ import org.apache.xmlgraphics.java2d.color.ColorUtil;
 import org.apache.xmlgraphics.java2d.color.ColorWithAlternatives;
 
 import org.apache.fop.afp.fonts.CharactersetEncoder.EncodedChars;
+import org.apache.fop.afp.modca.AxisOrientation;
+import org.apache.fop.afp.ptoca.TransparentDataControlSequence.TransparentData;
 
 /**
  * Generator class for PTOCA data structures.
@@ -87,8 +89,10 @@ public abstract class PtocaBuilder implements PtocaConstants {
         baout.writeTo(out);
     }
 
-    private void writeByte(int data) {
-        baout.write(data);
+    private void writeBytes(int... data) {
+        for (int d : data) {
+            baout.write(d);
+        }
     }
 
     private void writeShort(int data) {
@@ -123,7 +127,7 @@ public abstract class PtocaBuilder implements PtocaConstants {
         }
 
         newControlSequence();
-        writeByte(font);
+        writeBytes(font);
         commit(chained(SCFL));
     }
 
@@ -187,26 +191,11 @@ public abstract class PtocaBuilder implements PtocaConstants {
      * @throws IOException if an I/O error occurs
      */
     public void addTransparentData(EncodedChars encodedChars) throws IOException {
-
-        // data size greater than TRANSPARENT_MAX_SIZE, so slice
-        int numTransData = encodedChars.getLength() / TRANSPARENT_DATA_MAX_SIZE;
-        int currIndex = 0;
-        for (int transDataCnt = 0; transDataCnt < numTransData; transDataCnt++) {
-            addTransparentDataChunk(encodedChars, currIndex, TRANSPARENT_DATA_MAX_SIZE);
-            currIndex += TRANSPARENT_DATA_MAX_SIZE;
+        for (TransparentData trn : new TransparentDataControlSequence(encodedChars)) {
+            newControlSequence();
+            trn.writeTo(baout);
+            commit(chained(TRN));
         }
-        int left = encodedChars.getLength() - currIndex;
-        addTransparentDataChunk(encodedChars, currIndex, left);
-
-    }
-
-
-
-    private void addTransparentDataChunk(EncodedChars encodedChars, int offset, int length)
-            throws IOException {
-        newControlSequence();
-        encodedChars.writeTo(baout, offset, length);
-        commit(chained(TRN));
     }
 
     /**
@@ -222,7 +211,7 @@ public abstract class PtocaBuilder implements PtocaConstants {
         newControlSequence();
         writeShort(length); // Rule length
         writeShort(width); // Rule width
-        writeByte(0); // Rule width fraction is always null. enough?
+        writeBytes(0); // Rule width fraction is always null. enough?
         commit(chained(DBR));
     }
 
@@ -239,7 +228,7 @@ public abstract class PtocaBuilder implements PtocaConstants {
         newControlSequence();
         writeShort(length); // Rule length
         writeShort(width); // Rule width
-        writeByte(0); // Rule width fraction is always null. enough?
+        writeBytes(0); // Rule width fraction is always null. enough?
         commit(chained(DIR));
     }
 
@@ -260,32 +249,7 @@ public abstract class PtocaBuilder implements PtocaConstants {
             return;
         }
         newControlSequence();
-        switch (orientation) {
-        case 90:
-            writeByte(0x2D);
-            writeByte(0x00);
-            writeByte(0x5A);
-            writeByte(0x00);
-            break;
-        case 180:
-            writeByte(0x5A);
-            writeByte(0x00);
-            writeByte(0x87);
-            writeByte(0x00);
-            break;
-        case 270:
-            writeByte(0x87);
-            writeByte(0x00);
-            writeByte(0x00);
-            writeByte(0x00);
-            break;
-        default:
-            writeByte(0x00);
-            writeByte(0x00);
-            writeByte(0x2D);
-            writeByte(0x00);
-            break;
-        }
+        AxisOrientation.getRightHandedAxisOrientationFor(orientation).writeTo(baout);
         commit(chained(STO));
         this.currentOrientation = orientation;
         currentX = -1;
@@ -317,55 +281,30 @@ public abstract class PtocaBuilder implements PtocaConstants {
 
         newControlSequence();
         if (col.getColorSpace().getType() == ColorSpace.TYPE_CMYK) {
-            writeByte(0x00); // Reserved; must be zero
-            writeByte(0x04); // Color space - 0x04 = CMYK
-            writeByte(0x00); // Reserved; must be zero
-            writeByte(0x00); // Reserved; must be zero
-            writeByte(0x00); // Reserved; must be zero
-            writeByte(0x00); // Reserved; must be zero
-            writeByte(8); // Number of bits in component 1
-            writeByte(8); // Number of bits in component 2
-            writeByte(8); // Number of bits in component 3
-            writeByte(8); // Number of bits in component 4
+            // Color space - 0x04 = CMYK, all else are reserved and must be zero
+            writeBytes(0x00, 0x04, 0x00, 0x00, 0x00, 0x00);
+            writeBytes(8, 8, 8, 8); // Number of bits in component 1, 2, 3 & 4 respectively
             float[] comps = col.getColorComponents(null);
             assert comps.length == 4;
             for (int i = 0; i < 4; i++) {
                 int component = Math.round(comps[i] * 255);
-                writeByte(component);
+                writeBytes(component);
             }
         } else if (cs instanceof CIELabColorSpace) {
-            writeByte(0x00); // Reserved; must be zero
-            writeByte(0x08); // Color space - 0x08 = CIELAB
-            writeByte(0x00); // Reserved; must be zero
-            writeByte(0x00); // Reserved; must be zero
-            writeByte(0x00); // Reserved; must be zero
-            writeByte(0x00); // Reserved; must be zero
-            writeByte(8); // Number of bits in component 1
-            writeByte(8); // Number of bits in component 2
-            writeByte(8); // Number of bits in component 3
-            writeByte(0); // Number of bits in component 4
+            // Color space - 0x08 = CIELAB, all else are reserved and must be zero
+            writeBytes(0x00, 0x08, 0x00, 0x00, 0x00, 0x00);
+            writeBytes(8, 8, 8, 0); // Number of bits in component 1,2,3 & 4
             //Sadly, 16 bit components don't seem to work
             float[] colorComponents = col.getColorComponents(null);
             int l = Math.round(colorComponents[0] * 255f);
             int a = Math.round(colorComponents[1] * 255f) - 128;
             int b = Math.round(colorComponents[2] * 255f) - 128;
-            writeByte(l); // L*
-            writeByte(a); // a*
-            writeByte(b); // b*
+            writeBytes(l, a, b); // l*, a* and b*
         } else {
-            writeByte(0x00); // Reserved; must be zero
-            writeByte(0x01); // Color space - 0x01 = RGB
-            writeByte(0x00); // Reserved; must be zero
-            writeByte(0x00); // Reserved; must be zero
-            writeByte(0x00); // Reserved; must be zero
-            writeByte(0x00); // Reserved; must be zero
-            writeByte(8); // Number of bits in component 1
-            writeByte(8); // Number of bits in component 2
-            writeByte(8); // Number of bits in component 3
-            writeByte(0); // Number of bits in component 4
-            writeByte(col.getRed()); // Red intensity
-            writeByte(col.getGreen()); // Green intensity
-            writeByte(col.getBlue()); // Blue intensity
+            // Color space - 0x01 = RGB, all else are reserved and must be zero
+            writeBytes(0x00, 0x01, 0x00, 0x00, 0x00, 0x00);
+            writeBytes(8, 8, 8, 0); // Number of bits in component 1, 2, 3 & 4 respectively
+            writeBytes(col.getRed(), col.getGreen(), col.getBlue()); // RGB intensity
         }
         commit(chained(SEC));
         this.currentColor = col;
@@ -407,7 +346,7 @@ public abstract class PtocaBuilder implements PtocaConstants {
         assert incr >= Short.MIN_VALUE && incr <= Short.MAX_VALUE;
         newControlSequence();
         writeShort(Math.abs(incr)); //Increment
-        writeByte(incr >= 0 ? 0 : 1); // Direction
+        writeBytes(incr >= 0 ? 0 : 1); // Direction
         commit(chained(SIA));
 
         this.currentInterCharacterAdjustment = incr;
index 2e692af2cf3fcfb07e7a573647dad44363e08488..c53b97fd04bf315c3ece32111da62b9f9ccffd8d 100644 (file)
@@ -64,6 +64,6 @@ public interface PtocaConstants {
     byte NOP = (byte)0xF8;
 
     /** Maximum size of transparent data chunks */
-    int TRANSPARENT_DATA_MAX_SIZE = 253;
+    int TRANSPARENT_DATA_MAX_SIZE = 253; // max length = 255 (minus the ControlSequence length)
 
 }
diff --git a/src/java/org/apache/fop/afp/ptoca/TransparentDataControlSequence.java b/src/java/org/apache/fop/afp/ptoca/TransparentDataControlSequence.java
new file mode 100644 (file)
index 0000000..4b42768
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * 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.afp.ptoca;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.fop.afp.fonts.CharactersetEncoder.EncodedChars;
+import org.apache.fop.afp.ptoca.TransparentDataControlSequence.TransparentData;
+
+import static org.apache.fop.afp.ptoca.PtocaConstants.TRANSPARENT_DATA_MAX_SIZE;
+
+/**
+ * This object represents a series of PTOCA TransparentData (TRN) control sequences. This implements
+ * {@link Iterable} to enable iteration through the TRNs.
+ */
+final class TransparentDataControlSequence implements Iterable<TransparentData> {
+
+    private static final int MAX_SBCS_TRN_SIZE = TRANSPARENT_DATA_MAX_SIZE;
+    // The maximum size of a TRN must be an EVEN number so that we're splitting TRNs on character
+    // boundaries rather than in the middle of a double-byte character
+    private static final int MAX_DBCS_TRN_SIZE = MAX_SBCS_TRN_SIZE - 1;
+
+    static final class TransparentData {
+        private final int offset;
+        private final int length;
+        private final EncodedChars encodedChars;
+
+        private TransparentData(int offset, int length, EncodedChars encChars) {
+            this.offset = offset;
+            this.length = length;
+            this.encodedChars = encChars;
+        }
+
+        void writeTo(OutputStream outStream) throws IOException {
+            encodedChars.writeTo(outStream, offset, length);
+        }
+    }
+
+    private final List<TransparentData> trns;
+
+    /**
+     * Converts an encoded String wrapped in an {@link EncodedChars} into a series of
+     * {@link TransparentData} control sequences.
+     *
+     * @param encChars the encoded characters to convert to TRNs
+     */
+    public TransparentDataControlSequence(EncodedChars encChars) {
+        int maxTrnLength = encChars.isDBCS() ? MAX_DBCS_TRN_SIZE : MAX_SBCS_TRN_SIZE;
+        int numTransData = encChars.getLength() / maxTrnLength;
+        int currIndex = 0;
+        List<TransparentData> trns = new ArrayList<TransparentData>();
+        for (int transDataCnt = 0; transDataCnt < numTransData; transDataCnt++) {
+            trns.add(new TransparentData(currIndex, maxTrnLength, encChars));
+            currIndex += maxTrnLength;
+        }
+        int left = encChars.getLength() - currIndex;
+        trns.add(new TransparentData(currIndex, left, encChars));
+        this.trns = Collections.unmodifiableList(trns);
+    }
+
+    /**
+     * The {@link Iterator} for retrieving the series of TRN control sequences.
+     */
+    public Iterator<TransparentData> iterator() {
+        return trns.iterator();
+    }
+}
diff --git a/test/java/org/apache/fop/afp/ptoca/TransparentDataControlSequenceTestCase.java b/test/java/org/apache/fop/afp/ptoca/TransparentDataControlSequenceTestCase.java
new file mode 100644 (file)
index 0000000..338c5e6
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * 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.afp.ptoca;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.junit.Test;
+
+import org.apache.fop.afp.fonts.CharactersetEncoder.EncodedChars;
+import org.apache.fop.afp.ptoca.TransparentDataControlSequence.TransparentData;
+
+import static org.apache.fop.afp.ptoca.PtocaConstants.TRANSPARENT_DATA_MAX_SIZE;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class TransparentDataControlSequenceTestCase {
+
+    private EncodedChars encodedChars;
+    private final OutputStream outStream = mock(OutputStream.class);
+
+    @Test
+    public void testSingleByteCharacterSet() throws IOException {
+        testTRNs(false);
+    }
+
+    @Test
+    public void testDoubleByteCharacterSets() throws IOException {
+        testTRNs(true);
+    }
+
+    public void testTRNs(boolean isDBCS) throws IOException {
+        for (int length = 100; length < 10000; length += 1000) {
+            createTRNControlSequence(isDBCS, length);
+            int maxTRNSize = TRANSPARENT_DATA_MAX_SIZE - (isDBCS ? 1 : 0);
+            int numberOfTRNs = length / maxTRNSize;
+            for (int i = 0; i < numberOfTRNs; i++) {
+                verify(encodedChars, times(1)).writeTo(outStream, i * maxTRNSize, maxTRNSize);
+            }
+            int lastOffset = numberOfTRNs * maxTRNSize;
+            verify(encodedChars, times(1)).writeTo(outStream, numberOfTRNs * maxTRNSize,
+                    length - lastOffset);
+        }
+    }
+
+    private void createTRNControlSequence(boolean isDBCS, int length) throws IOException {
+        encodedChars = mock(EncodedChars.class);
+        when(encodedChars.isDBCS()).thenReturn(isDBCS);
+        when(encodedChars.getLength()).thenReturn(length);
+        for (TransparentData trn : new TransparentDataControlSequence(encodedChars)) {
+            trn.writeTo(outStream);
+        }
+    }
+}