blob: 645a461d8ec720ababc620d9c10f4708d56950b1 [file] [log] [blame]
/*
* 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.awt.Color;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.fop.afp.AFPLineDataInfo;
import org.apache.fop.afp.AFPTextDataInfo;
import org.apache.fop.afp.util.BinaryUtils;
/**
* Presentation text data contains the graphic characters and the control
* sequences necessary to position the characters within the object space. The
* data consists of: - graphic characters to be presented - control sequences
* that position them - modal control sequences that adjust the positions by
* small amounts - other functions causing text to be presented with differences
* in appearance.
*
* The graphic characters are expected to conform to a coded font representation
* so that they can be translated from the code point in the object data to the
* character in the coded font. The units of measure for linear displacements
* are derived from the PresentationTextDescriptor or from the hierarchical
* defaults.
*
* In addition to graphic character code points, Presentation Text data can
* contain embedded control sequences. These are strings of two or more bytes
* which signal an alternate mode of processing for the content of the current
* Presentation Text data.
*
*/
public class PresentationTextData extends AbstractAFPObject {
/** the maximum size of the presentation text data.*/
private static final int MAX_SIZE = 8192;
/** the AFP data relating to this presentation text data. */
private final ByteArrayOutputStream baos = new ByteArrayOutputStream();
/** the current x coordinate. */
private int currentX = -1;
/** the current y cooridnate */
private int currentY = -1;
/** the current font */
private String currentFont = "";
/** the current orientation */
private int currentOrientation = 0;
/** the current color */
private Color currentColor = new Color(0, 0, 0);
/** the current variable space increment */
private int currentVariableSpaceCharacterIncrement = 0;
/** the current inter character adjustment */
private int currentInterCharacterAdjustment = 0;
/**
* Default constructor for the PresentationTextData.
*/
public PresentationTextData() {
this(false);
}
/**
* Constructor for the PresentationTextData, the boolean flag indicate
* whether the control sequence prefix should be set to indicate the start
* of a new control sequence.
*
* @param controlInd
* The control sequence indicator.
*/
public PresentationTextData(boolean controlInd) {
final byte[] data = {
0x5A, // Structured field identifier
0x00, // Record length byte 1
0x00, // Record length byte 2
SF_CLASS, // PresentationTextData identifier byte 1
Type.DATA, // PresentationTextData identifier byte 2
Category.PRESENTATION_TEXT, // PresentationTextData identifier byte 3
0x00, // Flag
0x00, // Reserved
0x00, // Reserved
};
baos.write(data, 0, 9);
if (controlInd) {
baos.write(new byte[] {0x2B, (byte) 0xD3}, 0, 2);
}
}
/**
* The Set Coded Font Local control sequence activates a coded font and
* specifies the character attributes to be used. This is a modal control
* sequence.
*
* @param font
* The font local identifier.
* @param afpdata
* The output stream to which data should be written.
*/
private void setCodedFont(byte font, ByteArrayOutputStream afpdata) {
// Avoid unnecessary specification of the font
if (String.valueOf(font).equals(currentFont)) {
return;
} else {
currentFont = String.valueOf(font);
}
afpdata.write(new byte[] {0x03, (byte) 0xF1, font}, 0, 3);
}
/**
* Establishes the current presentation position on the baseline at a new
* I-axis coordinate, which is a specified number of measurement units from
* the B-axis. There is no change to the current B-axis coordinate.
*
* @param coordinate
* The coordinate for the inline move.
* @param afpdata
* The output stream to which data should be written.
*/
private void absoluteMoveInline(int coordinate,
ByteArrayOutputStream afpdata) {
byte[] b = BinaryUtils.convert(coordinate, 2);
afpdata.write(new byte[] {0x04, (byte) 0xC7, b[0], b[1]}, 0, 4);
currentX = coordinate;
}
/**
* Establishes the baseline and the current presentation position at a new
* B-axis coordinate, which is a specified number of measurement units from
* the I-axis. There is no change to the current I-axis coordinate.
*
* @param coordinate
* The coordinate for the baseline move.
* @param afpdata
* The output stream to which data should be written.
*/
private void absoluteMoveBaseline(int coordinate,
ByteArrayOutputStream afpdata) {
byte[] b = BinaryUtils.convert(coordinate, 2);
afpdata.write(new byte[] {0x04, (byte) 0xD3, b[0], b[1]}, 0, 4);
currentY = coordinate;
}
private static final int TRANSPARENT_MAX_SIZE = 253;
/**
* The Transparent Data control sequence contains a sequence of code points
* that are presented without a scan for embedded control sequences.
*
* @param data
* The text data to add.
* @param afpdata
* The output stream to which data should be written.
*/
private void addTransparentData(byte[] data, ByteArrayOutputStream afpdata) {
// Calculate the length
int l = data.length + 2;
if (l > 255) {
// Check that we are not exceeding the maximum length
throw new IllegalArgumentException(
"Transparent data is longer than " + TRANSPARENT_MAX_SIZE + " bytes: " + data);
}
afpdata.write(new byte[] {BinaryUtils.convert(l)[0], (byte) 0xDB},
0, 2);
afpdata.write(data, 0, data.length);
}
/**
* Draws a line of specified length and specified width in the B-direction
* from the current presentation position. The location of the current
* presentation position is unchanged.
*
* @param length
* The length of the rule.
* @param width
* The width of the rule.
* @param afpdata
* The output stream to which data should be written.
*/
private void drawBaxisRule(int length, int width,
ByteArrayOutputStream afpdata) {
afpdata.write(new byte[] {
0x07, // Length
(byte) 0xE7, // Type
}, 0, 2);
// Rule length
byte[] data1 = BinaryUtils.shortToByteArray((short) length);
afpdata.write(data1, 0, data1.length);
// Rule width
byte[] data2 = BinaryUtils.shortToByteArray((short) width);
afpdata.write(data2, 0, data2.length);
// Rule width fraction
afpdata.write(0x00);
}
/**
* Draws a line of specified length and specified width in the I-direction
* from the current presentation position. The location of the current
* presentation position is unchanged.
*
* @param length
* The length of the rule.
* @param width
* The width of the rule.
* @param afpdata
* The output stream to which data should be written.
*/
private void drawIaxisRule(int length, int width,
ByteArrayOutputStream afpdata) {
afpdata.write(new byte[] {
0x07, // Length
(byte) 0xE5, // Type
}, 0, 2);
// Rule length
byte[] data1 = BinaryUtils.shortToByteArray((short) length);
afpdata.write(data1, 0, data1.length);
// Rule width
byte[] data2 = BinaryUtils.shortToByteArray((short) width);
afpdata.write(data2, 0, data2.length);
// Rule width fraction
afpdata.write(0x00);
}
/**
* Create the presentation text data for the byte array of data.
*
* @param textDataInfo
* the afp text data
* @throws MaximumSizeExceededException
* thrown if the maximum number of text data is exceeded
* @throws UnsupportedEncodingException
* thrown if character encoding is not supported
*/
public void createTextData(AFPTextDataInfo textDataInfo)
throws MaximumSizeExceededException, UnsupportedEncodingException {
ByteArrayOutputStream afpdata = new ByteArrayOutputStream();
int rotation = textDataInfo.getRotation();
if (currentOrientation != rotation) {
setTextOrientation(rotation, afpdata);
currentOrientation = rotation;
currentX = -1;
currentY = -1;
}
// Avoid unnecessary specification of the Y coordinate
int y = textDataInfo.getY();
if (currentY != y) {
absoluteMoveBaseline(y, afpdata);
currentX = -1;
}
// Avoid unnecessary specification of the X coordinate
int x = textDataInfo.getX();
if (currentX != x) {
absoluteMoveInline(x, afpdata);
}
// Avoid unnecessary specification of the variable space increment
if (textDataInfo.getVariableSpaceCharacterIncrement()
!= currentVariableSpaceCharacterIncrement) {
setVariableSpaceCharacterIncrement(textDataInfo
.getVariableSpaceCharacterIncrement(), afpdata);
currentVariableSpaceCharacterIncrement = textDataInfo
.getVariableSpaceCharacterIncrement();
}
// Avoid unnecessary specification of the inter character adjustment
if (textDataInfo.getInterCharacterAdjustment() != currentInterCharacterAdjustment) {
setInterCharacterAdjustment(textDataInfo.getInterCharacterAdjustment(),
afpdata);
currentInterCharacterAdjustment = textDataInfo
.getInterCharacterAdjustment();
}
// Avoid unnecessary specification of the text color
if (!textDataInfo.getColor().equals(currentColor)) {
setExtendedTextColor(textDataInfo.getColor(), afpdata);
currentColor = textDataInfo.getColor();
}
setCodedFont(BinaryUtils.convert(textDataInfo.getFontReference())[0],
afpdata);
// Add transparent data
String textString = textDataInfo.getString();
String encoding = textDataInfo.getEncoding();
byte[] data = textString.getBytes(encoding);
if (data.length <= TRANSPARENT_MAX_SIZE) {
addTransparentData(data, afpdata);
} else {
// data size greater than TRANSPARENT_MAX_SIZE so slice
int numTransData = data.length / TRANSPARENT_MAX_SIZE;
byte[] buff = new byte[TRANSPARENT_MAX_SIZE];
int currIndex = 0;
for (int transDataCnt = 0; transDataCnt < numTransData; transDataCnt++) {
currIndex = transDataCnt * TRANSPARENT_MAX_SIZE;
System.arraycopy(data, currIndex, buff, 0, TRANSPARENT_MAX_SIZE);
addTransparentData(buff, afpdata);
}
int remainingTransData = data.length / TRANSPARENT_MAX_SIZE;
buff = new byte[remainingTransData];
System.arraycopy(data, currIndex, buff, 0, remainingTransData);
addTransparentData(buff, afpdata);
}
currentX = -1;
int dataSize = afpdata.size();
if (baos.size() + dataSize > MAX_SIZE) {
currentX = -1;
currentY = -1;
throw new MaximumSizeExceededException();
}
byte[] outputdata = afpdata.toByteArray();
baos.write(outputdata, 0, outputdata.length);
}
private int ensurePositive(int value) {
if (value < 0) {
return 0;
}
return value;
}
/**
* Drawing of lines using the starting and ending coordinates, thickness and
* colour arguments.
*
* @param lineDataInfo the line data information.
* @throws MaximumSizeExceededException
* thrown if the maximum number of line data has been exceeded
*/
public void createLineData(AFPLineDataInfo lineDataInfo) throws MaximumSizeExceededException {
ByteArrayOutputStream afpdata = new ByteArrayOutputStream();
int orientation = lineDataInfo.getRotation();
if (currentOrientation != orientation) {
setTextOrientation(orientation, afpdata);
currentOrientation = orientation;
}
// Avoid unnecessary specification of the Y coordinate
int y1 = ensurePositive(lineDataInfo.getY1());
if (y1 != currentY) {
absoluteMoveBaseline(y1, afpdata);
}
// Avoid unnecessary specification of the X coordinate
int x1 = ensurePositive(lineDataInfo.getX1());
if (x1 != currentX) {
absoluteMoveInline(x1, afpdata);
}
Color color = lineDataInfo.getColor();
if (!color.equals(currentColor)) {
setExtendedTextColor(color, afpdata);
currentColor = color;
}
int x2 = ensurePositive(lineDataInfo.getX2());
int y2 = ensurePositive(lineDataInfo.getY2());
int thickness = lineDataInfo.getThickness();
if (y1 == y2) {
drawIaxisRule(x2 - x1, thickness, afpdata);
} else if (x1 == x2) {
drawBaxisRule(y2 - y1, thickness, afpdata);
} else {
log.error("Invalid axis rule unable to draw line");
return;
}
int dataSize = afpdata.size();
if (baos.size() + dataSize > MAX_SIZE) {
currentX = -1;
currentY = -1;
throw new MaximumSizeExceededException();
}
byte[] outputdata = afpdata.toByteArray();
baos.write(outputdata, 0, outputdata.length);
}
/**
* The Set Text Orientation control sequence establishes the I-direction and
* B-direction for the subsequent text. This is a modal control sequence.
*
* Semantics: This control sequence specifies the I-axis and B-axis
* orientations with respect to the Xp-axis for the current Presentation
* Text object. The orientations are rotational values expressed in degrees
* and minutes.
*
* @param orientation
* The text orientation (0, 90, 180, 270).
* @param os
* The output stream to which data should be written.
*/
private void setTextOrientation(int orientation,
ByteArrayOutputStream os) {
os.write(new byte[] {0x06, (byte) 0xF7, }, 0, 2);
switch (orientation) {
case 90:
os.write(0x2D);
os.write(0x00);
os.write(0x5A);
os.write(0x00);
break;
case 180:
os.write(0x5A);
os.write(0x00);
os.write(0x87);
os.write(0x00);
break;
case 270:
os.write(0x87);
os.write(0x00);
os.write(0x00);
os.write(0x00);
break;
default:
os.write(0x00);
os.write(0x00);
os.write(0x2D);
os.write(0x00);
break;
}
}
/**
* The Set Extended Text Color control sequence specifies a color value and
* defines the color space and encoding for that value. The specified color
* value is applied to foreground areas of the text presentation space. This
* is a modal control sequence.
*
* @param col
* The color to be set.
* @param os
* The output stream to which data should be written.
*/
private void setExtendedTextColor(Color col, ByteArrayOutputStream os) {
byte[] colorData = new byte[] {
15, // Control sequence length
(byte) 0x81, // Control sequence function type
0x00, // Reserved; must be zero
0x01, // Color space - 0x01 = RGB
0x00, // Reserved; must be zero
0x00, // Reserved; must be zero
0x00, // Reserved; must be zero
0x00, // Reserved; must be zero
8, // Number of bits in component 1
8, // Number of bits in component 2
8, // Number of bits in component 3
0, // Number of bits in component 4
(byte) (col.getRed()), // Red intensity
(byte) (col.getGreen()), // Green intensity
(byte) (col.getBlue()), // Blue intensity
};
os.write(colorData, 0, colorData.length);
}
/**
* //TODO This is a modal control sequence.
*
* @param incr
* The increment to be set.
* @param os
* The output stream to which data should be written.
*/
private void setVariableSpaceCharacterIncrement(int incr,
ByteArrayOutputStream os) {
byte[] b = BinaryUtils.convert(incr, 2);
os.write(new byte[] {
4, // Control sequence length
(byte) 0xC5, // Control sequence function type
b[0], b[1] },
0, 4);
}
/**
* //TODO This is a modal control sequence.
*
* @param incr
* The increment to be set.
* @param os
* The output stream to which data should be written.
*/
private void setInterCharacterAdjustment(int incr, ByteArrayOutputStream os) {
byte[] b = BinaryUtils.convert(Math.abs(incr), 2);
os.write(new byte[] {
5, // Control sequence length
(byte) 0xC3, // Control sequence function type
b[0], b[1], (byte) (incr >= 0 ? 0 : 1) // Direction
}, 0, 5);
}
/** {@inheritDoc} */
public void writeToStream(OutputStream os) throws IOException {
byte[] data = baos.toByteArray();
byte[] size = BinaryUtils.convert(data.length - 1, 2);
data[1] = size[0];
data[2] = size[1];
os.write(data);
}
/**
* A control sequence is a sequence of bytes that specifies a control
* function. A control sequence consists of a control sequence introducer
* and zero or more parameters. The control sequence can extend multiple
* presentation text data objects, but must eventually be terminated. This
* method terminates the control sequence.
*
* @throws MaximumSizeExceededException
* thrown in the event that maximum size has been exceeded
*/
public void endControlSequence() throws MaximumSizeExceededException {
byte[] data = new byte[2];
data[0] = 0x02;
data[1] = (byte) 0xF8;
if (data.length + baos.size() > MAX_SIZE) {
throw new MaximumSizeExceededException();
}
baos.write(data, 0, data.length);
}
}