Browse Source

Changed the way AFP PTOCA TransparentData control sequences are written so that they end on character byte boundaries


git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1337142 13f79535-47bb-0310-9956-ffa450edef68
tags/fop-1_1rc1old
Mehdi Houshmand 12 years ago
parent
commit
c46fafb223

+ 20
- 31
src/java/org/apache/fop/afp/fonts/CharactersetEncoder.java View 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
*

+ 82
- 0
src/java/org/apache/fop/afp/modca/AxisOrientation.java View File

@@ -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");
}
}
}

+ 2
- 67
src/java/org/apache/fop/afp/modca/IncludeObject.java View 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;
}
}

}

+ 46
- 0
src/java/org/apache/fop/afp/modca/Rotation.java View File

@@ -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;
}
}

+ 27
- 88
src/java/org/apache/fop/afp/ptoca/PtocaBuilder.java View 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;

+ 1
- 1
src/java/org/apache/fop/afp/ptoca/PtocaConstants.java View 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)

}

+ 89
- 0
src/java/org/apache/fop/afp/ptoca/TransparentDataControlSequence.java View File

@@ -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();
}
}

+ 73
- 0
test/java/org/apache/fop/afp/ptoca/TransparentDataControlSequenceTestCase.java View File

@@ -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);
}
}
}

Loading…
Cancel
Save