@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);
}
}
@Override
EncodedChars getEncodedChars(byte[] byteArray, int length) {
- return new EncodedChars(byteArray);
+ return new EncodedChars(byteArray, false);
}
}
/**
* 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);
}
/**
* @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);
}
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
*
--- /dev/null
+/*
+ * 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");
+ }
+ }
+}
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;
* The orientation (0,90, 180, 270)
*/
public void setObjectAreaOrientation(int orientation) {
- this.oaOrent = ObjectAreaRotation.objectAreaRotationFor(orientation);
+ this.oaOrent = AxisOrientation.getRightHandedAxisOrientationFor(orientation);
}
/**
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;
- }
- }
-
}
--- /dev/null
+/*
+ * 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;
+ }
+}
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.
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) {
}
newControlSequence();
- writeByte(font);
+ writeBytes(font);
commit(chained(SCFL));
}
* @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));
}
/**
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));
}
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));
}
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;
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;
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;
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)
}
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+ }
+}