diff options
30 files changed, 2168 insertions, 635 deletions
diff --git a/src/java/META-INF/services/org.apache.fop.render.ImageHandler b/src/java/META-INF/services/org.apache.fop.render.ImageHandler index 730032927..549e8a1dc 100644 --- a/src/java/META-INF/services/org.apache.fop.render.ImageHandler +++ b/src/java/META-INF/services/org.apache.fop.render.ImageHandler @@ -13,3 +13,8 @@ org.apache.fop.render.ps.PSImageHandlerRawCCITTFax org.apache.fop.render.ps.PSImageHandlerRawJPEG
org.apache.fop.render.ps.PSImageHandlerGraphics2D
org.apache.fop.render.ps.PSImageHandlerSVG
+org.apache.fop.render.afp.AFPImageHandlerRenderedImage
+org.apache.fop.render.afp.AFPImageHandlerGraphics2D
+org.apache.fop.render.afp.AFPImageHandlerRawStream
+org.apache.fop.render.afp.AFPImageHandlerRawCCITTFax
+org.apache.fop.render.afp.AFPImageHandlerSVG
\ No newline at end of file diff --git a/src/java/META-INF/services/org.apache.fop.render.intermediate.IFDocumentHandler b/src/java/META-INF/services/org.apache.fop.render.intermediate.IFDocumentHandler index a6bf64ddf..efa5c7a3c 100644 --- a/src/java/META-INF/services/org.apache.fop.render.intermediate.IFDocumentHandler +++ b/src/java/META-INF/services/org.apache.fop.render.intermediate.IFDocumentHandler @@ -1,4 +1,5 @@ org.apache.fop.render.pdf.PDFDocumentHandlerMaker
org.apache.fop.render.pcl.PCLDocumentHandlerMaker
org.apache.fop.render.bitmap.TIFFDocumentHandlerMaker
-org.apache.fop.render.ps.PSDocumentHandlerMaker
\ No newline at end of file +org.apache.fop.render.ps.PSDocumentHandlerMaker
+org.apache.fop.render.afp.AFPDocumentHandlerMaker
\ No newline at end of file diff --git a/src/java/org/apache/fop/afp/modca/AbstractPageObject.java b/src/java/org/apache/fop/afp/modca/AbstractPageObject.java index eff879715..8ad8308a7 100644 --- a/src/java/org/apache/fop/afp/modca/AbstractPageObject.java +++ b/src/java/org/apache/fop/afp/modca/AbstractPageObject.java @@ -35,7 +35,7 @@ import org.apache.fop.afp.fonts.AFPFont; * page has a set of data objects associated with it. Each page within a * document is independent from any other page, and each must establish its own * environment parameters. - * + * <p> * The page is the level in the document component hierarchy that is used for * printing or displaying a document's content. The data objects contained in * the page envelope in the data stream are presented when the page is @@ -43,12 +43,11 @@ import org.apache.fop.afp.fonts.AFPFont; * directs the placement and orientation of the data on the page. In addition, * each page contains layout information that specifies the measurement units, * page width, and page depth. - * + * <p> * A page is initiated by a begin page structured field and terminated by an end * page structured field. Structured fields that define objects and active * environment groups or that specify attributes of the page may be encountered * in page state. - * */ public abstract class AbstractPageObject extends AbstractNamedAFPObject implements Completable { @@ -205,7 +204,7 @@ public abstract class AbstractPageObject extends AbstractNamedAFPObject implemen * * @return the presentation text object */ - private PresentationTextObject getPresentationTextObject() { + public PresentationTextObject getPresentationTextObject() { if (currentPresentationTextObject == null) { PresentationTextObject presentationTextObject = factory.createPresentationTextObject(); diff --git a/src/java/org/apache/fop/afp/modca/PresentationTextData.java b/src/java/org/apache/fop/afp/modca/PresentationTextData.java index d71f54687..2b56caf1a 100644 --- a/src/java/org/apache/fop/afp/modca/PresentationTextData.java +++ b/src/java/org/apache/fop/afp/modca/PresentationTextData.java @@ -19,15 +19,13 @@ 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.ptoca.PtocaBuilder; +import org.apache.fop.afp.ptoca.PtocaConstants; import org.apache.fop.afp.util.BinaryUtils; /** @@ -37,20 +35,21 @@ import org.apache.fop.afp.util.BinaryUtils; * that position them - modal control sequences that adjust the positions by * small amounts - other functions causing text to be presented with differences * in appearance. - * + * <p> * 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. - * + * <p> * 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. - * + * <p> + * The content for this object can be created using {@link PtocaBuilder}. */ -public class PresentationTextData extends AbstractAFPObject { +public class PresentationTextData extends AbstractAFPObject implements PtocaConstants { /** the maximum size of the presentation text data.*/ private static final int MAX_SIZE = 8192; @@ -58,27 +57,6 @@ public class PresentationTextData extends AbstractAFPObject { /** 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. */ @@ -86,6 +64,8 @@ public class PresentationTextData extends AbstractAFPObject { this(false); } + private static final int HEADER_LENGTH = 9; + /** * Constructor for the PresentationTextData, the boolean flag indicate * whether the control sequence prefix should be set to indicate the start @@ -106,7 +86,7 @@ public class PresentationTextData extends AbstractAFPObject { 0x00, // Reserved 0x00, // Reserved }; - baos.write(data, 0, 9); + baos.write(data, 0, HEADER_LENGTH); if (controlInd) { baos.write(new byte[] {0x2B, (byte) 0xD3}, 0, 2); @@ -114,420 +94,25 @@ public class PresentationTextData extends AbstractAFPObject { } /** - * 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. + * Returns the number of data bytes still available in this object until it is full and a new + * one has to be started. + * @return the number of data bytes available */ - 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); + public int getBytesAvailable() { + return MAX_SIZE - baos.size() + HEADER_LENGTH; } /** - * 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. + * Returns the output stream the content data is written to. + * @return the output stream */ - 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++) { - System.arraycopy(data, currIndex, buff, 0, TRANSPARENT_MAX_SIZE); - addTransparentData(buff, afpdata); - currIndex += TRANSPARENT_MAX_SIZE; - } - int left = data.length - currIndex; - buff = new byte[left]; - System.arraycopy(data, currIndex, buff, 0, left); - 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); + protected OutputStream getOutputStream() { + return this.baos; } /** {@inheritDoc} */ public void writeToStream(OutputStream os) throws IOException { + assert getBytesAvailable() >= 0; byte[] data = baos.toByteArray(); byte[] size = BinaryUtils.convert(data.length - 1, 2); data[1] = size[0]; @@ -535,23 +120,4 @@ public class PresentationTextData extends AbstractAFPObject { 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); - } }
\ No newline at end of file diff --git a/src/java/org/apache/fop/afp/modca/PresentationTextObject.java b/src/java/org/apache/fop/afp/modca/PresentationTextObject.java index 4a8bbbb18..3a455a04b 100644 --- a/src/java/org/apache/fop/afp/modca/PresentationTextObject.java +++ b/src/java/org/apache/fop/afp/modca/PresentationTextObject.java @@ -26,6 +26,10 @@ import java.util.List; import org.apache.fop.afp.AFPLineDataInfo; import org.apache.fop.afp.AFPTextDataInfo; +import org.apache.fop.afp.ptoca.LineDataInfoProducer; +import org.apache.fop.afp.ptoca.PtocaBuilder; +import org.apache.fop.afp.ptoca.PtocaProducer; +import org.apache.fop.afp.ptoca.TextDataInfoProducer; /** * The Presentation Text object is the data object used in document processing @@ -41,6 +45,8 @@ import org.apache.fop.afp.AFPTextDataInfo; * collection of the graphic characters and control codes is called Presentation * Text, and the object that contains the Presentation Text is called the * PresentationText object. + * <p> + * The content for this object can be created using {@link PtocaBuilder}. */ public class PresentationTextObject extends AbstractNamedAFPObject { @@ -54,6 +60,8 @@ public class PresentationTextObject extends AbstractNamedAFPObject { */ private List/*<PresentationTextData>*/ presentationTextDataList = null; + private PtocaBuilder builder = new DefaultBuilder(); + /** * Construct a new PresentationTextObject for the specified name argument, * the name should be an 8 character identifier. @@ -72,17 +80,37 @@ public class PresentationTextObject extends AbstractNamedAFPObject { * @throws UnsupportedEncodingException thrown if character encoding is not supported */ public void createTextData(AFPTextDataInfo textDataInfo) throws UnsupportedEncodingException { + createControlSequences(new TextDataInfoProducer(textDataInfo)); + } + + /** + * Creates a chain of control sequences using a producer. + * @param producer the producer + * @throws UnsupportedEncodingException thrown if character encoding is not supported + */ + public void createControlSequences(PtocaProducer producer) + throws UnsupportedEncodingException { if (currentPresentationTextData == null) { startPresentationTextData(); } try { - currentPresentationTextData.createTextData(textDataInfo); - } catch (MaximumSizeExceededException msee) { - endPresentationTextData(); - createTextData(textDataInfo); + producer.produce(builder); } catch (UnsupportedEncodingException e) { endPresentationTextData(); throw e; + } catch (IOException ioe) { + endPresentationTextData(); + handleUnexpectedIOError(ioe); + } + } + + private class DefaultBuilder extends PtocaBuilder { + protected OutputStream getOutputStreamForControlSequence(int length) { + if (length > currentPresentationTextData.getBytesAvailable()) { + endPresentationTextData(); + startPresentationTextData(); + } + return currentPresentationTextData.getOutputStream(); } } @@ -93,14 +121,10 @@ public class PresentationTextObject extends AbstractNamedAFPObject { * @param lineDataInfo the line data information. */ public void createLineData(AFPLineDataInfo lineDataInfo) { - if (currentPresentationTextData == null) { - startPresentationTextData(); - } try { - currentPresentationTextData.createLineData(lineDataInfo); - } catch (MaximumSizeExceededException msee) { - endPresentationTextData(); - createLineData(lineDataInfo); + createControlSequences(new LineDataInfoProducer(lineDataInfo)); + } catch (UnsupportedEncodingException e) { + handleUnexpectedIOError(e); //Won't happen for lines } } @@ -157,18 +181,24 @@ public class PresentationTextObject extends AbstractNamedAFPObject { startPresentationTextData(); } try { - currentPresentationTextData.endControlSequence(); - } catch (MaximumSizeExceededException msee) { + builder.endChainedControlSequence(); + } catch (IOException ioe) { endPresentationTextData(); - endControlSequence(); + handleUnexpectedIOError(ioe); + //Should not occur since we're writing to byte arrays } } + private void handleUnexpectedIOError(IOException ioe) { + //"Unexpected" since we're currently dealing with ByteArrayOutputStreams here. + throw new RuntimeException("Unexpected I/O error", ioe); + } + /** {@inheritDoc} */ public String toString() { if (presentationTextDataList != null) { return presentationTextDataList.toString(); } - return null; + return super.toString(); } } diff --git a/src/java/org/apache/fop/afp/ptoca/LineDataInfoProducer.java b/src/java/org/apache/fop/afp/ptoca/LineDataInfoProducer.java new file mode 100644 index 000000000..c702d72a8 --- /dev/null +++ b/src/java/org/apache/fop/afp/ptoca/LineDataInfoProducer.java @@ -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.afp.ptoca; + +import java.io.IOException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.fop.afp.AFPLineDataInfo; + +/** + * {@link PtocaProducer} implementation that interprets {@link AFPLineDataInfo} objects. + */ +public class LineDataInfoProducer implements PtocaProducer, PtocaConstants { + + /** Static logging instance */ + private static final Log log = LogFactory.getLog(LineDataInfoProducer.class); + + private AFPLineDataInfo lineDataInfo; + + /** + * Main constructor. + * @param lineDataInfo the info object + */ + public LineDataInfoProducer(AFPLineDataInfo lineDataInfo) { + this.lineDataInfo = lineDataInfo; + } + + /** {@inheritDoc} */ + public void produce(PtocaBuilder builder) throws IOException { + builder.setTextOrientation(lineDataInfo.getRotation()); + int x1 = ensurePositive(lineDataInfo.getX1()); + int y1 = ensurePositive(lineDataInfo.getY1()); + builder.absoluteMoveBaseline(y1); + builder.absoluteMoveInline(x1); + builder.setExtendedTextColor(lineDataInfo.getColor()); + + int x2 = ensurePositive(lineDataInfo.getX2()); + int y2 = ensurePositive(lineDataInfo.getY2()); + int thickness = lineDataInfo.getThickness(); + if (y1 == y2) { + builder.drawIaxisRule(x2 - x1, thickness); + } else if (x1 == x2) { + builder.drawBaxisRule(y2 - y1, thickness); + } else { + log.error("Invalid axis rule: unable to draw line"); + return; + } + } + + private static int ensurePositive(int value) { + if (value < 0) { + return 0; + } + return value; + } + +} diff --git a/src/java/org/apache/fop/afp/ptoca/PtocaBuilder.java b/src/java/org/apache/fop/afp/ptoca/PtocaBuilder.java new file mode 100644 index 000000000..b5e866380 --- /dev/null +++ b/src/java/org/apache/fop/afp/ptoca/PtocaBuilder.java @@ -0,0 +1,389 @@ +/* + * 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.awt.Color; +import java.io.IOException; +import java.io.OutputStream; + +import org.apache.commons.io.output.ByteArrayOutputStream; + +/** + * Generator class for PTOCA data structures. + */ +public abstract class PtocaBuilder implements PtocaConstants { + + private ByteArrayOutputStream baout = new ByteArrayOutputStream(256); + + /** the current x coordinate. */ + private int currentX = -1; + + /** the current y coordinate */ + private int currentY = -1; + + /** the current font */ + private int currentFont = Integer.MIN_VALUE; + + /** the current orientation */ + private int currentOrientation = 0; + + /** the current color */ + private Color currentColor = Color.BLACK; + + /** the current variable space increment */ + private int currentVariableSpaceCharacterIncrement = 0; + + /** the current inter character adjustment */ + private int currentInterCharacterAdjustment = 0; + + + /** + * Returns an {@link OutputStream} for the next control sequence. This gives a subclass a + * chance to do chunking of control sequences into multiple presentation text data objects. + * @param length the length of the following control sequence + * @return the output stream where the control sequence will be written to + */ + protected abstract OutputStream getOutputStreamForControlSequence(int length); + + private static byte chained(byte functionType) { + return (byte)(functionType | CHAIN_BIT); + } + + private void newControlSequence() { + baout.reset(); + } + + private void commit(byte functionType) throws IOException { + int length = baout.size() + 2; + assert length < 256; + + OutputStream out = getOutputStreamForControlSequence(length); + out.write(length); + out.write(functionType); + baout.writeTo(out); + } + + private void write(byte[] data, int offset, int length) { + baout.write(data, offset, length); + } + + private void writeByte(int data) { + baout.write(data); + } + + private void writeShort(int data) { + baout.write((data >>> 8) & 0xFF); + baout.write(data & 0xFF); + } + + /** + * Writes the introducer for a chained control sequence. + * @throws IOException if an I/O error occurs + */ + public void writeIntroducer() throws IOException { + OutputStream out = getOutputStreamForControlSequence(ESCAPE.length); + out.write(ESCAPE); + } + + /** + * The Set Coded Font Local control sequence activates a coded font and + * specifies the character attributes to be used. + * <p> + * This is a modal control sequence. + * + * @param font The font local identifier. + * @throws IOException if an I/O error occurs + */ + public void setCodedFont(byte font) throws IOException { + // Avoid unnecessary specification of the font + if (currentFont == font) { + return; + } else { + currentFont = font; + } + + newControlSequence(); + writeByte(font); + commit(chained(SCFL)); + } + + /** + * 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. + * @throws IOException if an I/O error occurs + */ + public void absoluteMoveInline(int coordinate) throws IOException { + if (coordinate == this.currentX) { + return; + } + newControlSequence(); + writeShort(coordinate); + commit(chained(AMI)); + + currentX = coordinate; + } + + /** + * Moves the inline coordinate of the presentation position relative to the current + * inline position. + * @param increment the increment in 1/1440 inch units + * @throws IOException if an I/O error occurs + */ + public void relativeMoveInline(int increment) throws IOException { + newControlSequence(); + writeShort(increment); + commit(chained(RMI)); + } + + /** + * 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. + * @throws IOException if an I/O error occurs + */ + public void absoluteMoveBaseline(int coordinate) throws IOException { + if (coordinate == this.currentY) { + return; + } + newControlSequence(); + writeShort(coordinate); + commit(chained(AMB)); + + currentY = coordinate; + currentX = -1; + } + + 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. If the data is larger + * than fits in one chunk, additional chunks are automatically generated. + * + * @param data The text data to add. + * @throws IOException if an I/O error occurs + */ + public void addTransparentData(byte[] data) throws IOException { + if (data.length <= TRANSPARENT_DATA_MAX_SIZE) { + addTransparentDataChunk(data); + } else { + // data size greater than TRANSPARENT_MAX_SIZE, so slice + int numTransData = data.length / TRANSPARENT_DATA_MAX_SIZE; + int currIndex = 0; + for (int transDataCnt = 0; transDataCnt < numTransData; transDataCnt++) { + addTransparentDataChunk(data, currIndex, TRANSPARENT_DATA_MAX_SIZE); + currIndex += TRANSPARENT_DATA_MAX_SIZE; + } + int left = data.length - currIndex; + addTransparentDataChunk(data, currIndex, left); + } + } + + private void addTransparentDataChunk(byte[] data) throws IOException { + addTransparentDataChunk(data, 0, data.length); + } + + private void addTransparentDataChunk(byte[] data, int offset, int length) throws IOException { + if (length > TRANSPARENT_MAX_SIZE) { + // Check that we are not exceeding the maximum length + throw new IllegalArgumentException( + "Transparent data is longer than " + TRANSPARENT_MAX_SIZE + " bytes"); + } + newControlSequence(); + write(data, offset, length); + commit(chained(TRN)); + } + + /** + * 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. + * @throws IOException if an I/O error occurs + */ + public void drawBaxisRule(int length, int width) throws IOException { + newControlSequence(); + writeShort(length); // Rule length + writeShort(width); // Rule width + writeByte(0); // Rule width fraction is always null. enough? + commit(chained(DBR)); + } + + /** + * 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. + * @throws IOException if an I/O error occurs + */ + public void drawIaxisRule(int length, int width) throws IOException { + newControlSequence(); + writeShort(length); // Rule length + writeShort(width); // Rule width + writeByte(0); // Rule width fraction is always null. enough? + commit(chained(DIR)); + } + + /** + * 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). + * @throws IOException if an I/O error occurs + */ + public void setTextOrientation(int orientation) throws IOException { + if (orientation == this.currentOrientation) { + 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; + } + commit(chained(STO)); + this.currentOrientation = orientation; + currentX = -1; + currentY = -1; + } + + /** + * 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. + * <p> + * This is a modal control sequence. + * + * @param col The color to be set. + * @throws IOException if an I/O error occurs + */ + public void setExtendedTextColor(Color col) throws IOException { + if (col.equals(currentColor)) { + return; + } + newControlSequence(); + 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 + commit(chained(SEC)); + this.currentColor = col; + } + + /** + * Sets the variable space character increment. + * <p> + * This is a modal control sequence. + * + * @param incr The increment to be set (positive integer, 1/1440 inch) + * @throws IOException if an I/O error occurs + */ + public void setVariableSpaceCharacterIncrement(int incr) throws IOException { + if (incr == this.currentVariableSpaceCharacterIncrement) { + return; + } + assert incr > 0 && incr < (1 << 16); + newControlSequence(); + writeShort(Math.abs(incr)); //Increment + commit(chained(SVI)); + + this.currentVariableSpaceCharacterIncrement = incr; + } + + /** + * Sets the intercharacter adjustment (additional increment or decrement between graphic + * characters). + * <p> + * This is a modal control sequence. + * + * @param incr The increment to be set (1/1440 inch) + * @throws IOException if an I/O error occurs + */ + public void setInterCharacterAdjustment(int incr) throws IOException { + if (incr == this.currentInterCharacterAdjustment) { + return; + } + assert incr >= Short.MIN_VALUE && incr <= Short.MAX_VALUE; + newControlSequence(); + writeShort(Math.abs(incr)); //Increment + writeByte(incr >= 0 ? 0 : 1); // Direction + commit(chained(SIA)); + + this.currentInterCharacterAdjustment = incr; + } + + /** + * 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 (by using a NOP command). + * + * @throws IOException if an I/O error occurs + */ + public void endChainedControlSequence() throws IOException { + newControlSequence(); + commit(NOP); + } +} diff --git a/src/java/org/apache/fop/afp/ptoca/PtocaConstants.java b/src/java/org/apache/fop/afp/ptoca/PtocaConstants.java new file mode 100644 index 000000000..2e692af2c --- /dev/null +++ b/src/java/org/apache/fop/afp/ptoca/PtocaConstants.java @@ -0,0 +1,69 @@ +/* + * 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; + +/** + * A collection of PTOCA constants. + */ +public interface PtocaConstants { + + /** + * "Escape" sequence for normal PTOCA command sequences. + */ + byte[] ESCAPE = new byte[] {0x2B, (byte)0xD3}; + + /** Bit to set for chained control sequences */ + byte CHAIN_BIT = 1; + + /** Set Intercharacter Adjustment */ + byte SIA = (byte)0xC2; + /** Set Variable Space Character Increment */ + byte SVI = (byte)0xC4; + /** Absolute Move Inline */ + byte AMI = (byte)0xC6; + /** Relative Move Inline */ + byte RMI = (byte)0xC8; + + /** Absolute Move Baseline */ + byte AMB = (byte)0xD2; + + /** Transparent Data */ + byte TRN = (byte)0xDA; + + /** Draw I-axis Rule */ + byte DIR = (byte)0xE4; + /** Draw B-axis Rule */ + byte DBR = (byte)0xE6; + + /** Set Extended Text Color */ + byte SEC = (byte)0x80; + + /** Set Coded Font Local */ + byte SCFL = (byte)0xF0; + /** Set Text Orientation */ + byte STO = (byte)0xF6; + + /** No Operation */ + byte NOP = (byte)0xF8; + + /** Maximum size of transparent data chunks */ + int TRANSPARENT_DATA_MAX_SIZE = 253; + +} diff --git a/src/java/org/apache/fop/afp/ptoca/PtocaProducer.java b/src/java/org/apache/fop/afp/ptoca/PtocaProducer.java new file mode 100644 index 000000000..504c49658 --- /dev/null +++ b/src/java/org/apache/fop/afp/ptoca/PtocaProducer.java @@ -0,0 +1,39 @@ +/* + * 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 org.apache.fop.afp.modca.PresentationTextObject; + +/** + * Producer interface that is passed to a {@link PresentationTextObject} to produce PTOCA control + * sequences using a {@link PtocaBuilder}. + */ +public interface PtocaProducer { + + /** + * Produces the PTOCA control sequences by calling methods on {@link PtocaBuilder}. + * @param builder the builder object + * @throws IOException if an I/O error occurs + */ + void produce(PtocaBuilder builder) throws IOException; + +} diff --git a/src/java/org/apache/fop/afp/ptoca/TextDataInfoProducer.java b/src/java/org/apache/fop/afp/ptoca/TextDataInfoProducer.java new file mode 100644 index 000000000..7ae3028e8 --- /dev/null +++ b/src/java/org/apache/fop/afp/ptoca/TextDataInfoProducer.java @@ -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.afp.ptoca; + +import java.io.IOException; + +import org.apache.fop.afp.AFPTextDataInfo; + +/** + * {@link PtocaProducer} implementation that interprets {@link AFPTextDataInfo} objects. + */ +public class TextDataInfoProducer implements PtocaProducer, PtocaConstants { + + private AFPTextDataInfo textDataInfo; + + /** + * Main constructor. + * @param textDataInfo the info object + */ + public TextDataInfoProducer(AFPTextDataInfo textDataInfo) { + this.textDataInfo = textDataInfo; + } + + /** {@inheritDoc} */ + public void produce(PtocaBuilder builder) throws IOException { + builder.setTextOrientation(textDataInfo.getRotation()); + builder.absoluteMoveBaseline(textDataInfo.getY()); + builder.absoluteMoveInline(textDataInfo.getX()); + + builder.setVariableSpaceCharacterIncrement( + textDataInfo.getVariableSpaceCharacterIncrement()); + builder.setInterCharacterAdjustment( + textDataInfo.getInterCharacterAdjustment()); + builder.setExtendedTextColor(textDataInfo.getColor()); + builder.setCodedFont((byte)textDataInfo.getFontReference()); + + + // Add transparent data + String textString = textDataInfo.getString(); + String encoding = textDataInfo.getEncoding(); + byte[] data = textString.getBytes(encoding); + builder.addTransparentData(data); + } + +} diff --git a/src/java/org/apache/fop/afp/ptoca/package.html b/src/java/org/apache/fop/afp/ptoca/package.html new file mode 100644 index 000000000..7520e1cb2 --- /dev/null +++ b/src/java/org/apache/fop/afp/ptoca/package.html @@ -0,0 +1,23 @@ +<!-- + 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$ --> +<HTML> +<TITLE>org.apache.fop.afp.ptoca Package</TITLE> +<BODY> +<P>Contains a collection of classes for working with Presentation Text Objects (PTOCA).</P> +</BODY> +</HTML>
\ No newline at end of file diff --git a/src/java/org/apache/fop/render/AbstractRenderingContext.java b/src/java/org/apache/fop/render/AbstractRenderingContext.java index 365fec871..f6c68aafa 100644 --- a/src/java/org/apache/fop/render/AbstractRenderingContext.java +++ b/src/java/org/apache/fop/render/AbstractRenderingContext.java @@ -50,14 +50,19 @@ public abstract class AbstractRenderingContext implements RenderingContext { } /** {@inheritDoc} */ - public void putHints(Map hints) { - if (hints == null) { + public void putHints(Map additionalHints) { + if (additionalHints == null) { return; } if (this.hints == null) { this.hints = new java.util.HashMap(); } - this.hints.putAll(hints); + this.hints.putAll(additionalHints); + } + + /** {@inheritDoc} */ + public void putHint(Object key, Object value) { + this.hints.put(key, value); } /** {@inheritDoc} */ diff --git a/src/java/org/apache/fop/render/RenderingContext.java b/src/java/org/apache/fop/render/RenderingContext.java index 5122841b7..689227b4a 100644 --- a/src/java/org/apache/fop/render/RenderingContext.java +++ b/src/java/org/apache/fop/render/RenderingContext.java @@ -42,10 +42,30 @@ public interface RenderingContext { */ FOUserAgent getUserAgent(); - void putHints(Map hints); + /** + * Adds additional hints to the existing hints, overriding existing hints. + * @param additionalHints a map of additional hints + */ + void putHints(Map additionalHints); + + /** + * Sets an additional hint, overriding an existing hint. + * @param key the key + * @param value the value + */ + void putHint(Object key, Object value); + /** + * Returns an unmodifiable representation of all hints. + * @return the hints + */ Map getHints(); + /** + * Returns a hint identified by a key. + * @param key the key + * @return the hint or null if no hint with the given key could be found + */ Object getHint(Object key); } diff --git a/src/java/org/apache/fop/render/afp/AFPCustomizable.java b/src/java/org/apache/fop/render/afp/AFPCustomizable.java new file mode 100644 index 000000000..20588a579 --- /dev/null +++ b/src/java/org/apache/fop/render/afp/AFPCustomizable.java @@ -0,0 +1,72 @@ +/* + * 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.afp; + +/** + * Interface used to customize the AFP renderer or document handler. + */ +public interface AFPCustomizable { + + /** + * Sets the number of bits used per pixel + * + * @param bitsPerPixel + * number of bits per pixel + */ + void setBitsPerPixel(int bitsPerPixel); + + /** + * Sets whether images are color or not + * + * @param colorImages + * color image output + */ + void setColorImages(boolean colorImages); + + /** + * Sets whether images are supported natively or not + * + * @param nativeImages + * native image support + */ + void setNativeImagesSupported(boolean nativeImages); + + /** + * Sets the output/device resolution + * + * @param resolution + * the output resolution (dpi) + */ + void setResolution(int resolution); + + /** + * Returns the output/device resolution. + * + * @return the resolution in dpi + */ + int getResolution(); + + /** + * Sets the default resource group file path + * @param filePath the default resource group file path + */ + void setDefaultResourceGroupFilePath(String filePath); + +} diff --git a/src/java/org/apache/fop/render/afp/AFPDocumentHandler.java b/src/java/org/apache/fop/render/afp/AFPDocumentHandler.java new file mode 100644 index 000000000..b99ccf0c8 --- /dev/null +++ b/src/java/org/apache/fop/render/afp/AFPDocumentHandler.java @@ -0,0 +1,268 @@ +/* + * 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.afp; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.geom.AffineTransform; +import java.io.IOException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.fop.afp.AFPPaintingState; +import org.apache.fop.afp.AFPResourceManager; +import org.apache.fop.afp.AFPUnitConverter; +import org.apache.fop.afp.DataStream; +import org.apache.fop.afp.fonts.AFPPageFonts; +import org.apache.fop.apps.MimeConstants; +import org.apache.fop.render.intermediate.AbstractBinaryWritingIFDocumentHandler; +import org.apache.fop.render.intermediate.IFContext; +import org.apache.fop.render.intermediate.IFDocumentHandlerConfigurator; +import org.apache.fop.render.intermediate.IFException; +import org.apache.fop.render.intermediate.IFPainter; + +/** + * {@code IFDocumentHandler} implementation that produces AFP (MO:DCA). + */ +public class AFPDocumentHandler extends AbstractBinaryWritingIFDocumentHandler + implements AFPCustomizable { + + /** logging instance */ + private static Log log = LogFactory.getLog(AFPDocumentHandler.class); + + /** the resource manager */ + private AFPResourceManager resourceManager; + + /** the painting state */ + private final AFPPaintingState paintingState; + + /** unit converter */ + private final AFPUnitConverter unitConv; + + /** the AFP datastream */ + private DataStream dataStream; + + /** + * Default constructor. + */ + public AFPDocumentHandler() { + this.resourceManager = new AFPResourceManager(); + this.paintingState = new AFPPaintingState(); + this.unitConv = paintingState.getUnitConverter(); + } + + /** {@inheritDoc} */ + public boolean supportsPagesOutOfOrder() { + return false; + } + + /** {@inheritDoc} */ + public String getMimeType() { + return MimeConstants.MIME_AFP; + } + + /** {@inheritDoc} */ + public void setContext(IFContext context) { + super.setContext(context); + } + + /** {@inheritDoc} */ + public IFDocumentHandlerConfigurator getConfigurator() { + return new AFPRendererConfigurator(getUserAgent()); + } + + AFPPaintingState getPaintingState() { + return this.paintingState; + } + + DataStream getDataStream() { + return this.dataStream; + } + + AFPResourceManager getResourceManager() { + return this.resourceManager; + } + + /** {@inheritDoc} */ + public void startDocument() throws IFException { + try { + if (getUserAgent() == null) { + throw new IllegalStateException( + "User agent must be set before starting PostScript generation"); + } + if (this.outputStream == null) { + throw new IllegalStateException("OutputStream hasn't been set through setResult()"); + } + paintingState.setColor(Color.WHITE); + + this.dataStream = resourceManager.createDataStream(paintingState, outputStream); + + this.dataStream.startDocument(); + } catch (IOException e) { + throw new IFException("I/O error in startDocument()", e); + } + } + + /** {@inheritDoc} */ + public void endDocumentHeader() throws IFException { + } + + /** {@inheritDoc} */ + public void endDocument() throws IFException { + try { + this.dataStream.endDocument(); + this.dataStream = null; + this.resourceManager.writeToStream(); + this.resourceManager = null; + } catch (IOException ioe) { + throw new IFException("I/O error in endDocument()", ioe); + } + super.endDocument(); + } + + /** {@inheritDoc} */ + public void startPageSequence(String id) throws IFException { + try { + dataStream.startPageGroup(); + } catch (IOException ioe) { + throw new IFException("I/O error in startPageSequence()", ioe); + } + } + + /** {@inheritDoc} */ + public void endPageSequence() throws IFException { + //nop + } + + /** + * Returns the base AFP transform + * + * @return the base AFP transform + */ + private AffineTransform getBaseTransform() { + AffineTransform baseTransform = new AffineTransform(); + double scale = unitConv.mpt2units(1); + baseTransform.scale(scale, scale); + return baseTransform; + } + + /** {@inheritDoc} */ + public void startPage(int index, String name, String pageMasterName, Dimension size) + throws IFException { + paintingState.clear(); + + AffineTransform baseTransform = getBaseTransform(); + paintingState.concatenate(baseTransform); + + int pageWidth = Math.round(unitConv.mpt2units(size.width)); + paintingState.setPageWidth(pageWidth); + + int pageHeight = Math.round(unitConv.mpt2units(size.height)); + paintingState.setPageHeight(pageHeight); + + int pageRotation = paintingState.getPageRotation(); + int resolution = paintingState.getResolution(); + + dataStream.startPage(pageWidth, pageHeight, pageRotation, + resolution, resolution); + + //TODO Handle page extensions + //renderPageObjectExtensions(pageViewport); + + } + + /** {@inheritDoc} */ + public void startPageHeader() throws IFException { + super.startPageHeader(); + + } + + /** {@inheritDoc} */ + public void endPageHeader() throws IFException { + super.endPageHeader(); + } + + /** {@inheritDoc} */ + public IFPainter startPageContent() throws IFException { + return new AFPPainter(this); + } + + /** {@inheritDoc} */ + public void endPageContent() throws IFException { + } + + /** {@inheritDoc} */ + public void endPage() throws IFException { + try { + AFPPageFonts pageFonts = paintingState.getPageFonts(); + if (pageFonts != null && !pageFonts.isEmpty()) { + dataStream.addFontsToCurrentPage(pageFonts); + } + + dataStream.endPage(); + } catch (IOException ioe) { + throw new IFException("I/O error in endPage()", ioe); + } + } + + /** {@inheritDoc} */ + public void handleExtensionObject(Object extension) throws IFException { + /* + try { + } catch (IOException ioe) { + throw new IFException("I/O error in handleExtensionObject()", ioe); + } + */ + } + + // ---=== AFPCustomizable ===--- + + /** {@inheritDoc} */ + public void setBitsPerPixel(int bitsPerPixel) { + paintingState.setBitsPerPixel(bitsPerPixel); + } + + /** {@inheritDoc} */ + public void setColorImages(boolean colorImages) { + paintingState.setColorImages(colorImages); + } + + /** {@inheritDoc} */ + public void setNativeImagesSupported(boolean nativeImages) { + paintingState.setNativeImagesSupported(nativeImages); + } + + /** {@inheritDoc} */ + public void setResolution(int resolution) { + paintingState.setResolution(resolution); + } + + /** {@inheritDoc} */ + public int getResolution() { + return paintingState.getResolution(); + } + + /** {@inheritDoc} */ + public void setDefaultResourceGroupFilePath(String filePath) { + resourceManager.setDefaultResourceGroupFilePath(filePath); + } + +} diff --git a/src/java/org/apache/fop/render/afp/AFPDocumentHandlerMaker.java b/src/java/org/apache/fop/render/afp/AFPDocumentHandlerMaker.java new file mode 100644 index 000000000..b788a1011 --- /dev/null +++ b/src/java/org/apache/fop/render/afp/AFPDocumentHandlerMaker.java @@ -0,0 +1,60 @@ +/* + * 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.afp; + +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.apps.MimeConstants; +import org.apache.fop.render.intermediate.AbstractIFDocumentHandlerMaker; +import org.apache.fop.render.intermediate.IFContext; +import org.apache.fop.render.intermediate.IFDocumentHandler; +import org.apache.fop.render.intermediate.IFDocumentHandlerConfigurator; + +/** + * Intermediate format document handler factory for AFP output. + */ +public class AFPDocumentHandlerMaker extends AbstractIFDocumentHandlerMaker { + + //TODO Revert to normal MIME after stabilization! + private static final String[] MIMES = new String[] + {MimeConstants.MIME_AFP + ";mode=painter"}; + + /** {@inheritDoc} */ + public IFDocumentHandler makeIFDocumentHandler(FOUserAgent ua) { + AFPDocumentHandler handler = new AFPDocumentHandler(); + handler.setContext(new IFContext(ua)); + return handler; + } + + /** {@inheritDoc} */ + public boolean needsOutputStream() { + return true; + } + + /** {@inheritDoc} */ + public String[] getSupportedMimeTypes() { + return MIMES; + } + + /** {@inheritDoc} */ + public IFDocumentHandlerConfigurator getConfigurator(FOUserAgent userAgent) { + return new AFPRendererConfigurator(userAgent); + } + +} diff --git a/src/java/org/apache/fop/render/afp/AFPForeignAttributeReader.java b/src/java/org/apache/fop/render/afp/AFPForeignAttributeReader.java index 2b5077fe9..235218cfc 100644 --- a/src/java/org/apache/fop/render/afp/AFPForeignAttributeReader.java +++ b/src/java/org/apache/fop/render/afp/AFPForeignAttributeReader.java @@ -24,10 +24,12 @@ import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + +import org.apache.xmlgraphics.util.QName; + import org.apache.fop.afp.AFPResourceInfo; import org.apache.fop.afp.AFPResourceLevel; import org.apache.fop.render.afp.extensions.AFPElementMapping; -import org.apache.xmlgraphics.util.QName; /** * Parses any AFP foreign attributes @@ -36,13 +38,16 @@ public class AFPForeignAttributeReader { private static final Log log = LogFactory.getLog("org.apache.xmlgraphics.afp"); /** the resource-name attribute */ - public static final String RESOURCE_NAME = "afp:resource-name"; + public static final QName RESOURCE_NAME = new QName( + AFPElementMapping.NAMESPACE, "afp:resource-name"); /** the resource-level attribute */ - public static final String RESOURCE_LEVEL = "afp:resource-level"; + public static final QName RESOURCE_LEVEL = new QName( + AFPElementMapping.NAMESPACE, "afp:resource-level"); /** the resource-group-file attribute */ - public static final String RESOURCE_GROUP_FILE = "afp:resource-group-file"; + public static final QName RESOURCE_GROUP_FILE = new QName( + AFPElementMapping.NAMESPACE, "afp:resource-group-file"); /** * Main constructor @@ -59,8 +64,7 @@ public class AFPForeignAttributeReader { public AFPResourceInfo getResourceInfo(Map/*<QName, String>*/ foreignAttributes) { AFPResourceInfo resourceInfo = new AFPResourceInfo(); if (foreignAttributes != null && !foreignAttributes.isEmpty()) { - QName resourceNameKey = new QName(AFPElementMapping.NAMESPACE, RESOURCE_NAME); - String resourceName = (String)foreignAttributes.get(resourceNameKey); + String resourceName = (String)foreignAttributes.get(RESOURCE_NAME); if (resourceName != null) { resourceInfo.setName(resourceName); } @@ -81,16 +85,13 @@ public class AFPForeignAttributeReader { public AFPResourceLevel getResourceLevel(Map/*<QName, String>*/ foreignAttributes) { AFPResourceLevel resourceLevel = null; if (foreignAttributes != null && !foreignAttributes.isEmpty()) { - QName resourceLevelKey = new QName(AFPElementMapping.NAMESPACE, RESOURCE_LEVEL); - if (foreignAttributes.containsKey(resourceLevelKey)) { - String levelString = (String)foreignAttributes.get(resourceLevelKey); + if (foreignAttributes.containsKey(RESOURCE_LEVEL)) { + String levelString = (String)foreignAttributes.get(RESOURCE_LEVEL); resourceLevel = AFPResourceLevel.valueOf(levelString); // if external get resource group file attributes if (resourceLevel != null && resourceLevel.isExternal()) { - QName resourceGroupFileKey = new QName(AFPElementMapping.NAMESPACE, - RESOURCE_GROUP_FILE); String resourceGroupFile - = (String)foreignAttributes.get(resourceGroupFileKey); + = (String)foreignAttributes.get(RESOURCE_GROUP_FILE); if (resourceGroupFile == null) { String msg = RESOURCE_GROUP_FILE + " not specified"; log.error(msg); diff --git a/src/java/org/apache/fop/render/afp/AFPImageHandler.java b/src/java/org/apache/fop/render/afp/AFPImageHandler.java index a6d2d613d..4a985db00 100644 --- a/src/java/org/apache/fop/render/afp/AFPImageHandler.java +++ b/src/java/org/apache/fop/render/afp/AFPImageHandler.java @@ -20,6 +20,7 @@ package org.apache.fop.render.afp; import java.awt.Point; +import java.awt.Rectangle; import java.awt.geom.Rectangle2D; import java.io.IOException; import java.util.Map; @@ -55,33 +56,64 @@ public abstract class AFPImageHandler implements ImageHandlerBase { AFPDataObjectInfo dataObjectInfo = createDataObjectInfo(); // set resource information - Map foreignAttributes = rendererImageInfo.getForeignAttributes(); - AFPResourceInfo resourceInfo - = foreignAttributeReader.getResourceInfo(foreignAttributes); - resourceInfo.setUri(rendererImageInfo.getURI()); - dataObjectInfo.setResourceInfo(resourceInfo); + setResourceInformation(dataObjectInfo, + rendererImageInfo.getURI(), + rendererImageInfo.getForeignAttributes()); - // set object area - AFPObjectAreaInfo objectAreaInfo = new AFPObjectAreaInfo(); Point origin = rendererImageInfo.getOrigin(); Rectangle2D position = rendererImageInfo.getPosition(); - float srcX = origin.x + (float)position.getX(); - float srcY = origin.y + (float)position.getY(); + int srcX = Math.round(origin.x + (float)position.getX()); + int srcY = Math.round(origin.y + (float)position.getY()); + Rectangle targetRect = new Rectangle( + srcX, + srcY, + (int)Math.round(position.getWidth()), + (int)Math.round(position.getHeight())); AFPRendererContext rendererContext = (AFPRendererContext)rendererImageInfo.getRendererContext(); AFPInfo afpInfo = rendererContext.getInfo(); AFPPaintingState paintingState = afpInfo.getPaintingState(); + + dataObjectInfo.setObjectAreaInfo(createObjectAreaInfo(paintingState, targetRect)); + + return dataObjectInfo; + } + + /** + * Sets resource information on the data object info. + * @param dataObjectInfo the data object info instance + * @param uri the image's URI (or null if no URI is available) + * @param foreignAttributes a Map of foreign attributes (or null) + */ + protected void setResourceInformation(AFPDataObjectInfo dataObjectInfo, + String uri, Map foreignAttributes) { + AFPResourceInfo resourceInfo + = foreignAttributeReader.getResourceInfo(foreignAttributes); + resourceInfo.setUri(uri); + dataObjectInfo.setResourceInfo(resourceInfo); + } + + /** + * Creates and returns an {@link AFPObjectAreaInfo} instance for the placement of the image. + * @param paintingState the painting state + * @param targetRect the target rectangle in which to place the image (coordinates in mpt) + * @return the newly created object area info instance + */ + public static AFPObjectAreaInfo createObjectAreaInfo(AFPPaintingState paintingState, + Rectangle targetRect) { + AFPObjectAreaInfo objectAreaInfo = new AFPObjectAreaInfo(); AFPUnitConverter unitConv = paintingState.getUnitConverter(); - int[] coords = unitConv.mpts2units(new float[] {srcX, srcY}); + + int[] coords = unitConv.mpts2units(new float[] {targetRect.x, targetRect.y}); objectAreaInfo.setX(coords[X]); objectAreaInfo.setY(coords[Y]); - int width = Math.round(unitConv.mpt2units((float)position.getWidth())); + int width = Math.round(unitConv.mpt2units(targetRect.width)); objectAreaInfo.setWidth(width); - int height = Math.round(unitConv.mpt2units((float)position.getHeight())); + int height = Math.round(unitConv.mpt2units(targetRect.height)); objectAreaInfo.setHeight(height); int resolution = paintingState.getResolution(); @@ -89,10 +121,7 @@ public abstract class AFPImageHandler implements ImageHandlerBase { objectAreaInfo.setWidthRes(resolution); objectAreaInfo.setRotation(paintingState.getRotation()); - - dataObjectInfo.setObjectAreaInfo(objectAreaInfo); - - return dataObjectInfo; + return objectAreaInfo; } /** diff --git a/src/java/org/apache/fop/render/afp/AFPImageHandlerGraphics2D.java b/src/java/org/apache/fop/render/afp/AFPImageHandlerGraphics2D.java index 0780e8a59..572cc0415 100644 --- a/src/java/org/apache/fop/render/afp/AFPImageHandlerGraphics2D.java +++ b/src/java/org/apache/fop/render/afp/AFPImageHandlerGraphics2D.java @@ -19,23 +19,29 @@ package org.apache.fop.render.afp; +import java.awt.Rectangle; import java.io.IOException; +import org.apache.xmlgraphics.image.loader.Image; +import org.apache.xmlgraphics.image.loader.ImageFlavor; +import org.apache.xmlgraphics.image.loader.impl.ImageGraphics2D; +import org.apache.xmlgraphics.java2d.Graphics2DImagePainter; +import org.apache.xmlgraphics.util.MimeConstants; + import org.apache.fop.afp.AFPDataObjectInfo; import org.apache.fop.afp.AFPGraphics2D; import org.apache.fop.afp.AFPGraphicsObjectInfo; import org.apache.fop.afp.AFPPaintingState; import org.apache.fop.afp.AFPResourceInfo; import org.apache.fop.afp.AFPResourceLevel; -import org.apache.xmlgraphics.image.loader.ImageFlavor; -import org.apache.xmlgraphics.image.loader.impl.ImageGraphics2D; -import org.apache.xmlgraphics.java2d.Graphics2DImagePainter; -import org.apache.xmlgraphics.util.MimeConstants; +import org.apache.fop.render.ImageHandler; +import org.apache.fop.render.ImageHandlerUtil; +import org.apache.fop.render.RenderingContext; /** * PDFImageHandler implementation which handles Graphics2D images. */ -public class AFPImageHandlerGraphics2D extends AFPImageHandler { +public class AFPImageHandlerGraphics2D extends AFPImageHandler implements ImageHandler { private static final ImageFlavor[] FLAVORS = new ImageFlavor[] { ImageFlavor.GRAPHICS2D @@ -64,19 +70,13 @@ public class AFPImageHandlerGraphics2D extends AFPImageHandler { AFPGraphicsObjectInfo graphicsObjectInfo = (AFPGraphicsObjectInfo)super.generateDataObjectInfo(rendererImageInfo); - AFPResourceInfo resourceInfo = graphicsObjectInfo.getResourceInfo(); - //level not explicitly set/changed so default to inline for GOCA graphic objects - // (due to a bug in the IBM AFP Workbench Viewer (2.04.01.07), hard copy works just fine) - if (!resourceInfo.levelChanged()) { - resourceInfo.setLevel(new AFPResourceLevel(AFPResourceLevel.INLINE)); - } + setDefaultToInlineResourceLevel(graphicsObjectInfo); // set mime type (unsupported by MOD:CA registry) graphicsObjectInfo.setMimeType(MimeConstants.MIME_AFP_GOCA); // set g2d boolean textAsShapes = false; - AFPGraphics2D g2d = afpInfo.createGraphics2D(textAsShapes); graphicsObjectInfo.setGraphics2D(g2d); @@ -88,6 +88,15 @@ public class AFPImageHandlerGraphics2D extends AFPImageHandler { } } + private void setDefaultToInlineResourceLevel(AFPGraphicsObjectInfo graphicsObjectInfo) { + AFPResourceInfo resourceInfo = graphicsObjectInfo.getResourceInfo(); + //level not explicitly set/changed so default to inline for GOCA graphic objects + // (due to a bug in the IBM AFP Workbench Viewer (2.04.01.07), hard copy works just fine) + if (!resourceInfo.levelChanged()) { + resourceInfo.setLevel(new AFPResourceLevel(AFPResourceLevel.INLINE)); + } + } + /** {@inheritDoc} */ public int getPriority() { return 200; @@ -107,4 +116,54 @@ public class AFPImageHandlerGraphics2D extends AFPImageHandler { protected AFPDataObjectInfo createDataObjectInfo() { return new AFPGraphicsObjectInfo(); } + + /** {@inheritDoc} */ + public void handleImage(RenderingContext context, Image image, Rectangle pos) + throws IOException { + AFPRenderingContext afpContext = (AFPRenderingContext)context; + + AFPGraphicsObjectInfo graphicsObjectInfo = (AFPGraphicsObjectInfo)createDataObjectInfo(); + + // set resource information + setResourceInformation(graphicsObjectInfo, + image.getInfo().getOriginalURI(), + afpContext.getForeignAttributes()); + + // Positioning + graphicsObjectInfo.setObjectAreaInfo( + createObjectAreaInfo(afpContext.getPaintingState(), pos)); + + setDefaultToInlineResourceLevel(graphicsObjectInfo); + + // Image content + ImageGraphics2D imageG2D = (ImageGraphics2D)image; + boolean textAsShapes = false; //TODO Make configurable + AFPGraphics2D g2d = new AFPGraphics2D( + textAsShapes, + afpContext.getPaintingState(), + afpContext.getResourceManager(), + graphicsObjectInfo.getResourceInfo(), + afpContext.getFontInfo()); + g2d.setGraphicContext(new org.apache.xmlgraphics.java2d.GraphicContext()); + + graphicsObjectInfo.setGraphics2D(g2d); + graphicsObjectInfo.setPainter(imageG2D.getGraphics2DImagePainter()); + + // Create image + afpContext.getResourceManager().createObject(graphicsObjectInfo); + } + + /** {@inheritDoc} */ + public boolean isCompatible(RenderingContext targetContext, Image image) { + boolean supported = (image == null || image instanceof ImageGraphics2D) + && targetContext instanceof AFPRenderingContext; + if (supported) { + String mode = (String)targetContext.getHint(ImageHandlerUtil.CONVERSION_MODE); + if (ImageHandlerUtil.isConversionModeBitmap(mode)) { + //Disabling this image handler automatically causes a bitmap to be generated + return false; + } + } + return supported; + } } diff --git a/src/java/org/apache/fop/render/afp/AFPImageHandlerRawCCITTFax.java b/src/java/org/apache/fop/render/afp/AFPImageHandlerRawCCITTFax.java index 3ac1d5696..83d41ba8c 100644 --- a/src/java/org/apache/fop/render/afp/AFPImageHandlerRawCCITTFax.java +++ b/src/java/org/apache/fop/render/afp/AFPImageHandlerRawCCITTFax.java @@ -19,12 +19,15 @@ package org.apache.fop.render.afp; -import java.io.IOException; +import org.apache.xmlgraphics.image.loader.Image; +import org.apache.xmlgraphics.image.loader.ImageFlavor; +import org.apache.xmlgraphics.image.loader.impl.ImageRawCCITTFax; +import org.apache.xmlgraphics.image.loader.impl.ImageRawStream; +import org.apache.xmlgraphics.util.MimeConstants; import org.apache.fop.afp.AFPDataObjectInfo; import org.apache.fop.afp.AFPImageObjectInfo; -import org.apache.xmlgraphics.image.loader.ImageFlavor; -import org.apache.xmlgraphics.image.loader.impl.ImageRawCCITTFax; +import org.apache.fop.render.RenderingContext; /** * AFPImageHandler implementation which handles CCITT encoded images (CCITT fax group 3/4). @@ -36,17 +39,18 @@ public class AFPImageHandlerRawCCITTFax extends AbstractAFPImageHandlerRawStream }; /** {@inheritDoc} */ - public AFPDataObjectInfo generateDataObjectInfo( - AFPRendererImageInfo rendererImageInfo) throws IOException { - AFPImageObjectInfo imageObjectInfo - = (AFPImageObjectInfo)super.generateDataObjectInfo(rendererImageInfo); - - ImageRawCCITTFax ccitt = (ImageRawCCITTFax) rendererImageInfo.getImage(); + protected void setAdditionalParameters(AFPDataObjectInfo dataObjectInfo, + ImageRawStream image) { + AFPImageObjectInfo imageObjectInfo = (AFPImageObjectInfo)dataObjectInfo; + ImageRawCCITTFax ccitt = (ImageRawCCITTFax)image; int compression = ccitt.getCompression(); imageObjectInfo.setCompression(compression); imageObjectInfo.setBitsPerPixel(1); - return imageObjectInfo; + + //CCITTFax flavor doesn't have TIFF associated but the AFP library listens to + //that to identify CCITT encoded images. CCITT is not exclusive to TIFF. + imageObjectInfo.setMimeType(MimeConstants.MIME_TIFF); } /** {@inheritDoc} */ @@ -69,4 +73,14 @@ public class AFPImageHandlerRawCCITTFax extends AbstractAFPImageHandlerRawStream return FLAVORS; } + /** {@inheritDoc} */ + public boolean isCompatible(RenderingContext targetContext, Image image) { + if (targetContext instanceof AFPRenderingContext) { + AFPRenderingContext afpContext = (AFPRenderingContext)targetContext; + return (afpContext.getPaintingState().isNativeImagesSupported()) + && (image == null || image instanceof ImageRawCCITTFax); + } + return false; + } + } diff --git a/src/java/org/apache/fop/render/afp/AFPImageHandlerRawStream.java b/src/java/org/apache/fop/render/afp/AFPImageHandlerRawStream.java index ded9ec9d5..99ede8e79 100644 --- a/src/java/org/apache/fop/render/afp/AFPImageHandlerRawStream.java +++ b/src/java/org/apache/fop/render/afp/AFPImageHandlerRawStream.java @@ -19,10 +19,15 @@ package org.apache.fop.render.afp; -import org.apache.fop.afp.AFPDataObjectInfo; +import org.apache.xmlgraphics.image.loader.Image; import org.apache.xmlgraphics.image.loader.ImageFlavor; +import org.apache.xmlgraphics.image.loader.impl.ImageRawEPS; +import org.apache.xmlgraphics.image.loader.impl.ImageRawJPEG; import org.apache.xmlgraphics.image.loader.impl.ImageRawStream; +import org.apache.fop.afp.AFPDataObjectInfo; +import org.apache.fop.render.RenderingContext; + /** * AFPImageHandler implementation which handles raw stream images. */ @@ -52,4 +57,14 @@ public class AFPImageHandlerRawStream extends AbstractAFPImageHandlerRawStream { protected AFPDataObjectInfo createDataObjectInfo() { return new AFPDataObjectInfo(); } + + /** {@inheritDoc} */ + public boolean isCompatible(RenderingContext targetContext, Image image) { + if (targetContext instanceof AFPRenderingContext) { + AFPRenderingContext afpContext = (AFPRenderingContext)targetContext; + return (afpContext.getPaintingState().isNativeImagesSupported()) + && (image == null || image instanceof ImageRawJPEG || image instanceof ImageRawEPS); + } + return false; + } } diff --git a/src/java/org/apache/fop/render/afp/AFPImageHandlerRenderedImage.java b/src/java/org/apache/fop/render/afp/AFPImageHandlerRenderedImage.java index 28c942a08..a618b9edc 100644 --- a/src/java/org/apache/fop/render/afp/AFPImageHandlerRenderedImage.java +++ b/src/java/org/apache/fop/render/afp/AFPImageHandlerRenderedImage.java @@ -19,23 +19,29 @@ package org.apache.fop.render.afp; +import java.awt.Rectangle; import java.awt.image.RenderedImage; import java.io.IOException; import org.apache.commons.io.output.ByteArrayOutputStream; -import org.apache.fop.afp.AFPDataObjectInfo; -import org.apache.fop.afp.AFPImageObjectInfo; -import org.apache.fop.afp.AFPObjectAreaInfo; -import org.apache.fop.afp.AFPPaintingState; + +import org.apache.xmlgraphics.image.loader.Image; import org.apache.xmlgraphics.image.loader.ImageFlavor; import org.apache.xmlgraphics.image.loader.impl.ImageRendered; import org.apache.xmlgraphics.ps.ImageEncodingHelper; import org.apache.xmlgraphics.util.MimeConstants; +import org.apache.fop.afp.AFPDataObjectInfo; +import org.apache.fop.afp.AFPImageObjectInfo; +import org.apache.fop.afp.AFPObjectAreaInfo; +import org.apache.fop.afp.AFPPaintingState; +import org.apache.fop.render.ImageHandler; +import org.apache.fop.render.RenderingContext; + /** * PDFImageHandler implementation which handles RenderedImage instances. */ -public class AFPImageHandlerRenderedImage extends AFPImageHandler { +public class AFPImageHandlerRenderedImage extends AFPImageHandler implements ImageHandler { private static final ImageFlavor[] FLAVORS = new ImageFlavor[] { ImageFlavor.BUFFERED_IMAGE, @@ -52,13 +58,22 @@ public class AFPImageHandlerRenderedImage extends AFPImageHandler { = (AFPRendererContext)rendererImageInfo.getRendererContext(); AFPInfo afpInfo = rendererContext.getInfo(); AFPPaintingState paintingState = afpInfo.getPaintingState(); + ImageRendered imageRendered = (ImageRendered) rendererImageInfo.img; + + updateDataObjectInfo(imageObjectInfo, paintingState, imageRendered); + return imageObjectInfo; + } + + private AFPDataObjectInfo updateDataObjectInfo(AFPImageObjectInfo imageObjectInfo, + AFPPaintingState paintingState, ImageRendered imageRendered) + throws IOException { + int resolution = paintingState.getResolution(); imageObjectInfo.setMimeType(MimeConstants.MIME_AFP_IOCA_FS45); imageObjectInfo.setDataHeightRes(resolution); imageObjectInfo.setDataWidthRes(resolution); - ImageRendered imageRendered = (ImageRendered) rendererImageInfo.img; RenderedImage renderedImage = imageRendered.getRenderedImage(); int dataHeight = renderedImage.getHeight(); @@ -67,6 +82,13 @@ public class AFPImageHandlerRenderedImage extends AFPImageHandler { int dataWidth = renderedImage.getWidth(); imageObjectInfo.setDataWidth(dataWidth); + //TODO This needs to be improved: Buffering whole images in memory is bad. + //Images with fewer bits per pixel than the required output format are unnecessarily + //increased in size (extreme case: 1-bit images are blown up to 24 bits if color is + //enabled). For grayscale output, encoding is even a two-step process which + //needs to be streamlined to a single step. The resulting bitmap is then still buffered + //in memory. To reduce AFP file size, investigate using a compression scheme. + //Currently, all image data is uncompressed. ByteArrayOutputStream baos = new ByteArrayOutputStream(); ImageEncodingHelper.encodeRenderedImageAsRGB(renderedImage, baos); byte[] imageData = baos.toByteArray(); @@ -113,4 +135,33 @@ public class AFPImageHandlerRenderedImage extends AFPImageHandler { return FLAVORS; } + /** {@inheritDoc} */ + public void handleImage(RenderingContext context, Image image, Rectangle pos) + throws IOException { + AFPRenderingContext afpContext = (AFPRenderingContext)context; + + AFPImageObjectInfo imageObjectInfo = (AFPImageObjectInfo)createDataObjectInfo(); + + // set resource information + setResourceInformation(imageObjectInfo, + image.getInfo().getOriginalURI(), + afpContext.getForeignAttributes()); + + // Positioning + imageObjectInfo.setObjectAreaInfo(createObjectAreaInfo(afpContext.getPaintingState(), pos)); + + // Image content + ImageRendered imageRend = (ImageRendered)image; + updateDataObjectInfo(imageObjectInfo, afpContext.getPaintingState(), imageRend); + + // Create image + afpContext.getResourceManager().createObject(imageObjectInfo); + } + + /** {@inheritDoc} */ + public boolean isCompatible(RenderingContext targetContext, Image image) { + return (image == null || image instanceof ImageRendered) + && targetContext instanceof AFPRenderingContext; + } + } diff --git a/src/java/org/apache/fop/render/afp/AFPImageHandlerSVG.java b/src/java/org/apache/fop/render/afp/AFPImageHandlerSVG.java new file mode 100644 index 000000000..b0e92441b --- /dev/null +++ b/src/java/org/apache/fop/render/afp/AFPImageHandlerSVG.java @@ -0,0 +1,175 @@ +/* + * 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.afp; + +import java.awt.Dimension; +import java.awt.Rectangle; +import java.io.IOException; + +import org.w3c.dom.Document; + +import org.apache.batik.bridge.BridgeContext; +import org.apache.batik.bridge.GVTBuilder; +import org.apache.batik.gvt.GraphicsNode; + +import org.apache.xmlgraphics.image.loader.Image; +import org.apache.xmlgraphics.image.loader.ImageFlavor; +import org.apache.xmlgraphics.image.loader.impl.ImageXMLDOM; +import org.apache.xmlgraphics.java2d.Graphics2DImagePainter; + +import org.apache.fop.afp.AFPDataObjectInfo; +import org.apache.fop.afp.AFPGraphics2D; +import org.apache.fop.afp.AFPGraphicsObjectInfo; +import org.apache.fop.afp.AFPObjectAreaInfo; +import org.apache.fop.afp.AFPPaintingState; +import org.apache.fop.afp.AFPResourceInfo; +import org.apache.fop.afp.AFPResourceLevel; +import org.apache.fop.afp.AFPResourceManager; +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.image.loader.batik.BatikImageFlavors; +import org.apache.fop.image.loader.batik.BatikUtil; +import org.apache.fop.image.loader.batik.Graphics2DImagePainterImpl; +import org.apache.fop.render.ImageHandler; +import org.apache.fop.render.ImageHandlerUtil; +import org.apache.fop.render.RenderingContext; +import org.apache.fop.svg.SVGEventProducer; + +/** + * Image handler implementation which handles SVG images for AFP output. + * <p> + * Note: This class is not intended to be used as an {@link AFPImageHandler} but only as an + * {@link ImageHandler}. It subclasses {@link AFPImageHandler} only to get access to common + * methods. + */ +public class AFPImageHandlerSVG implements ImageHandler { + + private static final ImageFlavor[] FLAVORS = new ImageFlavor[] { + BatikImageFlavors.SVG_DOM + }; + + /** {@inheritDoc} */ + protected AFPDataObjectInfo createDataObjectInfo() { + return new AFPGraphicsObjectInfo(); + } + + /** {@inheritDoc} */ + public void handleImage(RenderingContext context, Image image, Rectangle pos) + throws IOException { + AFPRenderingContext afpContext = (AFPRenderingContext)context; + ImageXMLDOM imageSVG = (ImageXMLDOM)image; + FOUserAgent userAgent = afpContext.getUserAgent(); + + AFPGraphicsObjectInfo graphicsObjectInfo = (AFPGraphicsObjectInfo)createDataObjectInfo(); + AFPResourceInfo resourceInfo = graphicsObjectInfo.getResourceInfo(); + setDefaultToInlineResourceLevel(graphicsObjectInfo); + + // Create a new AFPGraphics2D + final boolean textAsShapes = false; //afpInfo.strokeText(); //TODO make configurable + AFPGraphics2D g2d = new AFPGraphics2D( + textAsShapes, + afpContext.getPaintingState(), + afpContext.getResourceManager(), + resourceInfo, + afpContext.getFontInfo()); + g2d.setGraphicContext(new org.apache.xmlgraphics.java2d.GraphicContext()); + + AFPPaintingState paintingState = g2d.getPaintingState(); + paintingState.setImageUri(image.getInfo().getOriginalURI()); + + // Create an AFPBridgeContext + BridgeContext bridgeContext = AFPSVGHandler.createBridgeContext(userAgent, g2d); + + // Cloning SVG DOM as Batik attaches non-thread-safe facilities (like the CSS engine) + // to it. + Document clonedDoc = BatikUtil.cloneSVGDocument(imageSVG.getDocument()); + + // Build the SVG DOM and provide the painter with it + GraphicsNode root; + try { + GVTBuilder builder = new GVTBuilder(); + root = builder.build(bridgeContext, clonedDoc); + } catch (Exception e) { + SVGEventProducer eventProducer = SVGEventProducer.Provider.get( + context.getUserAgent().getEventBroadcaster()); + eventProducer.svgNotBuilt(this, e, image.getInfo().getOriginalURI()); + return; + } + + paintingState.save(); // save + + // Image positioning + AFPObjectAreaInfo objectAreaInfo = AFPImageHandler.createObjectAreaInfo(paintingState, pos); + graphicsObjectInfo.setObjectAreaInfo(objectAreaInfo); + + //Set up painter and target + graphicsObjectInfo.setGraphics2D(g2d); + // Create Graphics2DImagePainter + Dimension imageSize = image.getSize().getDimensionMpt(); + Graphics2DImagePainter painter = new Graphics2DImagePainterImpl( + root, bridgeContext, imageSize); + graphicsObjectInfo.setPainter(painter); + + // Create the GOCA GraphicsObject in the DataStream + AFPResourceManager resourceManager = afpContext.getResourceManager(); + resourceManager.createObject(graphicsObjectInfo); + + paintingState.restore(); // resume + } + + private void setDefaultToInlineResourceLevel(AFPGraphicsObjectInfo graphicsObjectInfo) { + AFPResourceInfo resourceInfo = graphicsObjectInfo.getResourceInfo(); + //level not explicitly set/changed so default to inline for GOCA graphic objects + // (due to a bug in the IBM AFP Workbench Viewer (2.04.01.07), hard copy works just fine) + if (!resourceInfo.levelChanged()) { + resourceInfo.setLevel(new AFPResourceLevel(AFPResourceLevel.INLINE)); + } + } + + /** {@inheritDoc} */ + public int getPriority() { + return 400; + } + + /** {@inheritDoc} */ + public Class getSupportedImageClass() { + return ImageXMLDOM.class; + } + + /** {@inheritDoc} */ + public ImageFlavor[] getSupportedImageFlavors() { + return FLAVORS; + } + + /** {@inheritDoc} */ + public boolean isCompatible(RenderingContext targetContext, Image image) { + boolean supported = (image == null || (image instanceof ImageXMLDOM + && image.getFlavor().isCompatible(BatikImageFlavors.SVG_DOM))) + && targetContext instanceof AFPRenderingContext; + if (supported) { + String mode = (String)targetContext.getHint(ImageHandlerUtil.CONVERSION_MODE); + if (ImageHandlerUtil.isConversionModeBitmap(mode)) { + //Disabling this image handler automatically causes a bitmap to be generated + return false; + } + } + return supported; + } + +} diff --git a/src/java/org/apache/fop/render/afp/AFPPainter.java b/src/java/org/apache/fop/render/afp/AFPPainter.java new file mode 100644 index 000000000..286b3b2a9 --- /dev/null +++ b/src/java/org/apache/fop/render/afp/AFPPainter.java @@ -0,0 +1,342 @@ +/* + * 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.afp; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Paint; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.geom.AffineTransform; +import java.io.IOException; +import java.util.Map; + +import org.w3c.dom.Document; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.xmlgraphics.image.loader.ImageProcessingHints; +import org.apache.xmlgraphics.image.loader.ImageSessionContext; + +import org.apache.fop.afp.AFPBorderPainter; +import org.apache.fop.afp.AFPPaintingState; +import org.apache.fop.afp.AFPRectanglePainter; +import org.apache.fop.afp.AFPUnitConverter; +import org.apache.fop.afp.DataStream; +import org.apache.fop.afp.RectanglePaintingInfo; +import org.apache.fop.afp.fonts.AFPFont; +import org.apache.fop.afp.fonts.AFPFontAttributes; +import org.apache.fop.afp.fonts.AFPPageFonts; +import org.apache.fop.afp.fonts.CharacterSet; +import org.apache.fop.afp.modca.AbstractPageObject; +import org.apache.fop.afp.modca.PresentationTextObject; +import org.apache.fop.afp.ptoca.PtocaBuilder; +import org.apache.fop.afp.ptoca.PtocaProducer; +import org.apache.fop.fonts.FontInfo; +import org.apache.fop.fonts.FontTriplet; +import org.apache.fop.render.RenderingContext; +import org.apache.fop.render.intermediate.AbstractIFPainter; +import org.apache.fop.render.intermediate.IFContext; +import org.apache.fop.render.intermediate.IFException; +import org.apache.fop.render.intermediate.IFState; +import org.apache.fop.traits.BorderProps; +import org.apache.fop.traits.RuleStyle; + +/** + * IFPainter implementation that produces AFP (MO:DCA). + */ +public class AFPPainter extends AbstractIFPainter { + + /** logging instance */ + private static Log log = LogFactory.getLog(AFPPainter.class); + + private static final int X = 0; + private static final int Y = 1; + + private AFPDocumentHandler documentHandler; + + /** the line painter */ + private AFPBorderPainter borderPainter; + /** the rectangle painter */ + private AFPRectanglePainter rectanglePainter; + + /** unit converter */ + private final AFPUnitConverter unitConv; + + /** + * Default constructor. + * @param documentHandler the parent document handler + */ + public AFPPainter(AFPDocumentHandler documentHandler) { + super(); + this.documentHandler = documentHandler; + this.state = IFState.create(); + this.borderPainter = new AFPBorderPainter(getPaintingState(), getDataStream()); + this.rectanglePainter = new AFPRectanglePainter(getPaintingState(), getDataStream()); + this.unitConv = getPaintingState().getUnitConverter(); + } + + /** {@inheritDoc} */ + protected IFContext getContext() { + return this.documentHandler.getContext(); + } + + FontInfo getFontInfo() { + return this.documentHandler.getFontInfo(); + } + + AFPPaintingState getPaintingState() { + return this.documentHandler.getPaintingState(); + } + + DataStream getDataStream() { + return this.documentHandler.getDataStream(); + } + + /** {@inheritDoc} */ + public void startViewport(AffineTransform transform, Dimension size, Rectangle clipRect) + throws IFException { + //AFP doesn't support clipping, so we treat viewport like a group + //this is the same code as for startGroup() + try { + saveGraphicsState(); + concatenateTransformationMatrix(transform); + } catch (IOException ioe) { + throw new IFException("I/O error in startViewport()", ioe); + } + } + + /** {@inheritDoc} */ + public void endViewport() throws IFException { + try { + restoreGraphicsState(); + } catch (IOException ioe) { + throw new IFException("I/O error in endViewport()", ioe); + } + } + + private void concatenateTransformationMatrix(AffineTransform at) { + if (!at.isIdentity()) { + getPaintingState().concatenate(at); + } + } + + /** {@inheritDoc} */ + public void startGroup(AffineTransform transform) throws IFException { + try { + saveGraphicsState(); + concatenateTransformationMatrix(transform); + } catch (IOException ioe) { + throw new IFException("I/O error in startGroup()", ioe); + } + } + + /** {@inheritDoc} */ + public void endGroup() throws IFException { + try { + restoreGraphicsState(); + } catch (IOException ioe) { + throw new IFException("I/O error in endGroup()", ioe); + } + } + + /** {@inheritDoc} */ + protected Map createDefaultImageProcessingHints(ImageSessionContext sessionContext) { + Map hints = super.createDefaultImageProcessingHints(sessionContext); + + //AFP doesn't support alpha channels + hints.put(ImageProcessingHints.TRANSPARENCY_INTENT, + ImageProcessingHints.TRANSPARENCY_INTENT_IGNORE); + return hints; + } + + /** {@inheritDoc} */ + protected RenderingContext createRenderingContext() { + AFPRenderingContext psContext = new AFPRenderingContext( + getUserAgent(), + documentHandler.getResourceManager(), + getPaintingState(), + getFontInfo(), + getContext().getForeignAttributes()); + return psContext; + } + + /** {@inheritDoc} */ + public void drawImage(String uri, Rectangle rect) throws IFException { + drawImageUsingURI(uri, rect); + } + + /** {@inheritDoc} */ + public void drawImage(Document doc, Rectangle rect) throws IFException { + drawImageUsingDocument(doc, rect); + } + + /** {@inheritDoc} */ + public void clipRect(Rectangle rect) throws IFException { + //Not supported! + } + + private float toPoint(int mpt) { + return mpt / 1000f; + } + + /** {@inheritDoc} */ + public void fillRect(Rectangle rect, Paint fill) throws IFException { + if (fill == null) { + return; + } + if (rect.width != 0 && rect.height != 0) { + if (fill instanceof Color) { + getPaintingState().setColor((Color)fill); + } else { + throw new UnsupportedOperationException("Non-Color paints NYI"); + } + RectanglePaintingInfo rectanglePaintInfo = new RectanglePaintingInfo( + toPoint(rect.x), toPoint(rect.y), toPoint(rect.width), toPoint(rect.height)); + rectanglePainter.paint(rectanglePaintInfo); + } + } + + /** {@inheritDoc} */ + public void drawBorderRect(Rectangle rect, BorderProps before, BorderProps after, + BorderProps start, BorderProps end) throws IFException { + if (before != null || after != null || start != null || end != null) { + /* + try { + endTextObject(); + this.borderPainter.drawBorders(rect, before, after, start, end); + } catch (IOException ioe) { + throw new IFException("I/O error in drawBorderRect()", ioe); + }*/ + } + } + + /** {@inheritDoc} */ + public void drawLine(Point start, Point end, int width, Color color, RuleStyle style) + throws IFException { + /* + try { + endTextObject(); + this.borderPainter.drawLine(start, end, width, color, style); + } catch (IOException ioe) { + throw new IFException("I/O error in drawLine()", ioe); + }*/ + } + + /** {@inheritDoc} */ + public void drawText(int x, int y, final int[] dx, int[] dy, final String text) + throws IFException { + int fontSize = this.state.getFontSize(); + getPaintingState().setFontSize(fontSize); + + FontTriplet triplet = new FontTriplet( + state.getFontFamily(), state.getFontStyle(), state.getFontWeight()); + //TODO Ignored: state.getFontVariant() + String fontKey = getFontInfo().getInternalFontKey(triplet); + + // register font as necessary + Map/*<String,FontMetrics>*/ fontMetricMap = documentHandler.getFontInfo().getFonts(); + final AFPFont afpFont = (AFPFont)fontMetricMap.get(fontKey); + AFPPageFonts pageFonts = getPaintingState().getPageFonts(); + AFPFontAttributes fontAttributes = pageFonts.registerFont(fontKey, afpFont, fontSize); + + final int fontReference = fontAttributes.getFontReference(); + + final int[] coords = unitConv.mpts2units(new float[] {x, y} ); + + final CharacterSet charSet = afpFont.getCharacterSet(fontSize); + + AbstractPageObject page = getDataStream().getCurrentPage(); + PresentationTextObject pto = page.getPresentationTextObject(); + try { + pto.createControlSequences(new PtocaProducer() { + + public void produce(PtocaBuilder builder) throws IOException { + Point p = getPaintingState().getPoint(coords[X], coords[Y]); + builder.setTextOrientation(getPaintingState().getRotation()); + builder.absoluteMoveBaseline(p.y); + builder.absoluteMoveInline(p.x); + + builder.setVariableSpaceCharacterIncrement(0); + builder.setInterCharacterAdjustment(0); + builder.setExtendedTextColor(state.getTextColor()); + builder.setCodedFont((byte)fontReference); + + if (dx == null) { + //No glyph-shifting necessary, so take a shortcut + builder.addTransparentData(text.getBytes(charSet.getEncoding())); + } else { + int l = text.length(); + int dxl = dx.length; + StringBuffer sb = new StringBuffer(); + + if (dxl > 0 && dx[0] != 0) { + int dxu = Math.round(unitConv.mpt2units(dx[0])); + builder.relativeMoveInline(-dxu); + } + for (int i = 0; i < l; i++) { + sb.append(text.charAt(i)); + float glyphAdjust = 0; + + if (i < dxl - 1) { + glyphAdjust += dx[i + 1]; + } + + if (glyphAdjust != 0) { + if (sb.length() > 0) { + String t = sb.toString(); + builder.addTransparentData(t.getBytes(charSet.getEncoding())); + sb.setLength(0); + } + int increment = Math.round(unitConv.mpt2units(glyphAdjust)); + builder.relativeMoveInline(increment); + } + } + if (sb.length() > 0) { + String t = sb.toString(); + builder.addTransparentData(t.getBytes(charSet.getEncoding())); + } + } + } + + }); + } catch (IOException ioe) { + throw new IFException("I/O error in drawText()", ioe); + } + } + + /** + * Saves the graphics state of the rendering engine. + * @throws IOException if an I/O error occurs + */ + protected void saveGraphicsState() throws IOException { + getPaintingState().save(); + } + + /** + * Restores the last graphics state of the rendering engine. + * @throws IOException if an I/O error occurs + */ + protected void restoreGraphicsState() throws IOException { + getPaintingState().restore(); + } + +} diff --git a/src/java/org/apache/fop/render/afp/AFPRenderer.java b/src/java/org/apache/fop/render/afp/AFPRenderer.java index 8035a9490..5edb3b310 100644 --- a/src/java/org/apache/fop/render/afp/AFPRenderer.java +++ b/src/java/org/apache/fop/render/afp/AFPRenderer.java @@ -33,6 +33,14 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import org.apache.xmlgraphics.image.loader.ImageException; +import org.apache.xmlgraphics.image.loader.ImageFlavor; +import org.apache.xmlgraphics.image.loader.ImageInfo; +import org.apache.xmlgraphics.image.loader.ImageManager; +import org.apache.xmlgraphics.image.loader.ImageSessionContext; +import org.apache.xmlgraphics.image.loader.util.ImageUtil; +import org.apache.xmlgraphics.ps.ImageEncodingHelper; + import org.apache.fop.afp.AFPBorderPainter; import org.apache.fop.afp.AFPDataObjectInfo; import org.apache.fop.afp.AFPEventProducer; @@ -72,13 +80,6 @@ import org.apache.fop.render.Graphics2DAdapter; import org.apache.fop.render.RendererContext; import org.apache.fop.render.afp.extensions.AFPElementMapping; import org.apache.fop.render.afp.extensions.AFPPageSetup; -import org.apache.xmlgraphics.image.loader.ImageException; -import org.apache.xmlgraphics.image.loader.ImageFlavor; -import org.apache.xmlgraphics.image.loader.ImageInfo; -import org.apache.xmlgraphics.image.loader.ImageManager; -import org.apache.xmlgraphics.image.loader.ImageSessionContext; -import org.apache.xmlgraphics.image.loader.util.ImageUtil; -import org.apache.xmlgraphics.ps.ImageEncodingHelper; /** * This is an implementation of a FOP Renderer that renders areas to AFP. @@ -134,7 +135,7 @@ import org.apache.xmlgraphics.ps.ImageEncodingHelper; * (ie. at the start or end of the page). * */ -public class AFPRenderer extends AbstractPathOrientedRenderer { +public class AFPRenderer extends AbstractPathOrientedRenderer implements AFPCustomizable { private static final int X = 0; private static final int Y = 1; @@ -541,6 +542,7 @@ public class AFPRenderer extends AbstractPathOrientedRenderer { int textWordSpaceAdjust = text.getTextWordSpaceAdjust(); int textLetterSpaceAdjust = text.getTextLetterSpaceAdjust(); int textWidth = font.getWidth(' ', fontSize) / 1000; + textWidth = 0; //JM, the above is strange int variableSpaceCharacterIncrement = textWidth + textWordSpaceAdjust + textLetterSpaceAdjust; @@ -677,7 +679,7 @@ public class AFPRenderer extends AbstractPathOrientedRenderer { } /** - * Sets the rotation to be used for landsacpe pages, valid values are 0, 90, + * Sets the rotation to be used for landscape pages, valid values are 0, 90, * 180, 270 (default). * * @param rotation @@ -687,59 +689,34 @@ public class AFPRenderer extends AbstractPathOrientedRenderer { paintingState.setLandscapeRotation(rotation); } - /** - * Sets the number of bits used per pixel - * - * @param bitsPerPixel - * number of bits per pixel - */ + // ---=== AFPCustomizable ===--- + + /** {@inheritDoc} */ public void setBitsPerPixel(int bitsPerPixel) { paintingState.setBitsPerPixel(bitsPerPixel); } - /** - * Sets whether images are color or not - * - * @param colorImages - * color image output - */ + /** {@inheritDoc} */ public void setColorImages(boolean colorImages) { paintingState.setColorImages(colorImages); } - /** - * Sets whether images are supported natively or not - * - * @param nativeImages - * native image support - */ + /** {@inheritDoc} */ public void setNativeImagesSupported(boolean nativeImages) { paintingState.setNativeImagesSupported(nativeImages); } - /** - * Sets the output/device resolution - * - * @param resolution - * the output resolution (dpi) - */ + /** {@inheritDoc} */ public void setResolution(int resolution) { paintingState.setResolution(resolution); } - /** - * Returns the output/device resolution. - * - * @return the resolution in dpi - */ + /** {@inheritDoc} */ public int getResolution() { return paintingState.getResolution(); } - /** - * Sets the default resource group file path - * @param filePath the default resource group file path - */ + /** {@inheritDoc} */ public void setDefaultResourceGroupFilePath(String filePath) { resourceManager.setDefaultResourceGroupFilePath(filePath); } diff --git a/src/java/org/apache/fop/render/afp/AFPRendererConfigurator.java b/src/java/org/apache/fop/render/afp/AFPRendererConfigurator.java index 5982ae5b0..7c6ee5771 100644 --- a/src/java/org/apache/fop/render/afp/AFPRendererConfigurator.java +++ b/src/java/org/apache/fop/render/afp/AFPRendererConfigurator.java @@ -25,6 +25,7 @@ import java.util.List; import org.apache.avalon.framework.configuration.Configuration; import org.apache.avalon.framework.configuration.ConfigurationException; +import org.apache.fop.afp.fonts.AFPFontCollection; import org.apache.fop.afp.fonts.AFPFontInfo; import org.apache.fop.afp.fonts.CharacterSet; import org.apache.fop.afp.fonts.FopCharacterSet; @@ -32,17 +33,23 @@ import org.apache.fop.afp.fonts.OutlineFont; import org.apache.fop.afp.fonts.RasterFont; import org.apache.fop.apps.FOPException; import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.fonts.FontCollection; +import org.apache.fop.fonts.FontInfo; +import org.apache.fop.fonts.FontManager; import org.apache.fop.fonts.FontTriplet; import org.apache.fop.fonts.FontUtil; import org.apache.fop.fonts.Typeface; import org.apache.fop.render.PrintRendererConfigurator; import org.apache.fop.render.Renderer; +import org.apache.fop.render.intermediate.IFDocumentHandler; +import org.apache.fop.render.intermediate.IFDocumentHandlerConfigurator; import org.apache.fop.util.LogUtil; /** * AFP Renderer configurator */ -public class AFPRendererConfigurator extends PrintRendererConfigurator { +public class AFPRendererConfigurator extends PrintRendererConfigurator + implements IFDocumentHandlerConfigurator { /** * Default constructor @@ -231,6 +238,7 @@ public class AFPRendererConfigurator extends PrintRendererConfigurator { Configuration cfg = super.getRendererConfig(renderer); if (cfg != null) { AFPRenderer afpRenderer = (AFPRenderer)renderer; + try { List/*<AFPFontInfo>*/ fontList = buildFontListFromConfiguration(cfg); afpRenderer.setFontList(fontList); @@ -239,49 +247,88 @@ public class AFPRendererConfigurator extends PrintRendererConfigurator { userAgent.getFactory().validateUserConfigStrictly()); } - // image information - Configuration imagesCfg = cfg.getChild("images"); + configure(afpRenderer, cfg); + } + } + + private void configure(AFPCustomizable customizable, Configuration cfg) throws FOPException { + + // image information + Configuration imagesCfg = cfg.getChild("images"); + + // default to grayscale images + String imagesMode = imagesCfg.getAttribute("mode", IMAGES_MODE_GRAYSCALE); + if (IMAGES_MODE_COLOR.equals(imagesMode)) { + customizable.setColorImages(true); + } else { + customizable.setColorImages(false); + // default to 8 bits per pixel + int bitsPerPixel = imagesCfg.getAttributeAsInteger("bits-per-pixel", 8); + customizable.setBitsPerPixel(bitsPerPixel); + } + + // native image support + boolean nativeImageSupport = imagesCfg.getAttributeAsBoolean("native", false); + customizable.setNativeImagesSupported(nativeImageSupport); - // default to grayscale images - String imagesMode = imagesCfg.getAttribute("mode", IMAGES_MODE_GRAYSCALE); - if (IMAGES_MODE_COLOR.equals(imagesMode)) { - afpRenderer.setColorImages(true); + // renderer resolution + Configuration rendererResolutionCfg = cfg.getChild("renderer-resolution", false); + if (rendererResolutionCfg != null) { + customizable.setResolution(rendererResolutionCfg.getValueAsInteger(240)); + } + + // a default external resource group file setting + Configuration resourceGroupFileCfg + = cfg.getChild("resource-group-file", false); + if (resourceGroupFileCfg != null) { + String resourceGroupDest = null; + try { + resourceGroupDest = resourceGroupFileCfg.getValue(); + } catch (ConfigurationException e) { + LogUtil.handleException(log, e, + userAgent.getFactory().validateUserConfigStrictly()); + } + File resourceGroupFile = new File(resourceGroupDest); + if (resourceGroupFile.canWrite()) { + customizable.setDefaultResourceGroupFilePath(resourceGroupDest); } else { - afpRenderer.setColorImages(false); - // default to 8 bits per pixel - int bitsPerPixel = imagesCfg.getAttributeAsInteger("bits-per-pixel", 8); - afpRenderer.setBitsPerPixel(bitsPerPixel); + log.warn("Unable to write to default external resource group file '" + + resourceGroupDest + "'"); } + } + } - // native image support - boolean nativeImageSupport = imagesCfg.getAttributeAsBoolean("native", false); - afpRenderer.setNativeImagesSupported(nativeImageSupport); + /** {@inheritDoc} */ + public void configure(IFDocumentHandler documentHandler) throws FOPException { + Configuration cfg = super.getRendererConfig(documentHandler.getMimeType()); + if (cfg != null) { + AFPDocumentHandler afpDocumentHandler = (AFPDocumentHandler)documentHandler; + configure(afpDocumentHandler, cfg); + } + } - // renderer resolution - Configuration rendererResolutionCfg = cfg.getChild("renderer-resolution", false); - if (rendererResolutionCfg != null) { - afpRenderer.setResolution(rendererResolutionCfg.getValueAsInteger(240)); - } + /** {@inheritDoc} */ + public void setupFontInfo(IFDocumentHandler documentHandler, FontInfo fontInfo) + throws FOPException { + FontManager fontManager = userAgent.getFactory().getFontManager(); + List fontCollections = new java.util.ArrayList(); - // a default external resource group file setting - Configuration resourceGroupFileCfg - = cfg.getChild("resource-group-file", false); - if (resourceGroupFileCfg != null) { - String resourceGroupDest = null; - try { - resourceGroupDest = resourceGroupFileCfg.getValue(); - } catch (ConfigurationException e) { - LogUtil.handleException(log, e, - userAgent.getFactory().validateUserConfigStrictly()); - } - File resourceGroupFile = new File(resourceGroupDest); - if (resourceGroupFile.canWrite()) { - afpRenderer.setDefaultResourceGroupFilePath(resourceGroupDest); - } else { - log.warn("Unable to write to default external resource group file '" - + resourceGroupDest + "'"); - } + Configuration cfg = super.getRendererConfig(documentHandler.getMimeType()); + if (cfg != null) { + try { + List fontList = buildFontListFromConfiguration(cfg); + fontCollections.add(new AFPFontCollection( + userAgent.getEventBroadcaster(), fontList)); + } catch (ConfigurationException e) { + LogUtil.handleException(log, e, + userAgent.getFactory().validateUserConfigStrictly()); } + } + + fontManager.setup(fontInfo, + (FontCollection[])fontCollections.toArray( + new FontCollection[fontCollections.size()])); + documentHandler.setFontInfo(fontInfo); } } diff --git a/src/java/org/apache/fop/render/afp/AFPRendererContext.java b/src/java/org/apache/fop/render/afp/AFPRendererContext.java index 8d544a7c4..ab2e73779 100644 --- a/src/java/org/apache/fop/render/afp/AFPRendererContext.java +++ b/src/java/org/apache/fop/render/afp/AFPRendererContext.java @@ -22,14 +22,19 @@ package org.apache.fop.render.afp; import java.util.Map; import org.apache.avalon.framework.configuration.Configuration; + import org.apache.fop.afp.AFPPaintingState; import org.apache.fop.afp.AFPResourceInfo; import org.apache.fop.afp.AFPResourceLevel; import org.apache.fop.afp.AFPResourceManager; import org.apache.fop.render.AbstractRenderer; +import org.apache.fop.render.ImageHandlerUtil; import org.apache.fop.render.RendererContext; import org.apache.fop.render.RendererContextConstants; +/** + * AFP-specific renderer context class. + */ public class AFPRendererContext extends RendererContext { /** @@ -64,8 +69,7 @@ public class AFPRendererContext extends RendererContext { Map foreignAttributes = (Map)getProperty(RendererContextConstants.FOREIGN_ATTRIBUTES); if (foreignAttributes != null) { - String conversionMode = (String)foreignAttributes.get(CONVERSION_MODE); - boolean paintAsBitmap = BITMAP.equalsIgnoreCase(conversionMode); + boolean paintAsBitmap = ImageHandlerUtil.isConversionModeBitmap(foreignAttributes); info.setPaintAsBitmap(paintAsBitmap); AFPForeignAttributeReader foreignAttributeReader diff --git a/src/java/org/apache/fop/render/afp/AFPRenderingContext.java b/src/java/org/apache/fop/render/afp/AFPRenderingContext.java new file mode 100644 index 000000000..35be58c22 --- /dev/null +++ b/src/java/org/apache/fop/render/afp/AFPRenderingContext.java @@ -0,0 +1,93 @@ +/* + * 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.afp; + +import java.util.Map; + +import org.apache.xmlgraphics.util.MimeConstants; + +import org.apache.fop.afp.AFPPaintingState; +import org.apache.fop.afp.AFPResourceManager; +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.fonts.FontInfo; +import org.apache.fop.render.AbstractRenderingContext; + +/** + * Rendering context for AFP (MO:DCA) production. + */ +public class AFPRenderingContext extends AbstractRenderingContext { + + private AFPResourceManager resourceManager; + private AFPPaintingState paintingState; + private FontInfo fontInfo; + private Map foreignAttributes; + + /** + * Main constructor. + * @param userAgent the user agent + * @param resourceManager the resource manager + * @param fontInfo the font list + * @param foreignAttributes a map of foreign attributes + */ + public AFPRenderingContext(FOUserAgent userAgent, + AFPResourceManager resourceManager, + AFPPaintingState paintingState, + FontInfo fontInfo, Map foreignAttributes) { + super(userAgent); + this.resourceManager = resourceManager; + this.paintingState = paintingState; + this.fontInfo = fontInfo; + this.foreignAttributes = foreignAttributes; + } + + /** {@inheritDoc} */ + public String getMimeType() { + return MimeConstants.MIME_AFP; + } + + /** + * Returns the resource manager. + * @return the resource manager + */ + public AFPResourceManager getResourceManager() { + return this.resourceManager; + } + + public AFPPaintingState getPaintingState() { + return this.paintingState; + } + + /** + * Returns the font list. + * @return the font list + */ + public FontInfo getFontInfo() { + return this.fontInfo; + } + + /** + * Returns a Map of foreign attributes. + * @return the foreign attributes (Map<QName, Object>) + */ + public Map getForeignAttributes() { + return this.foreignAttributes; + } + +} diff --git a/src/java/org/apache/fop/render/afp/AFPSVGHandler.java b/src/java/org/apache/fop/render/afp/AFPSVGHandler.java index 161217a54..f76c4a89a 100644 --- a/src/java/org/apache/fop/render/afp/AFPSVGHandler.java +++ b/src/java/org/apache/fop/render/afp/AFPSVGHandler.java @@ -123,7 +123,7 @@ public class AFPSVGHandler extends AbstractGenericSVGHandler { = RendererContext.wrapRendererContext(rendererContext); Dimension imageSize = getImageSize(wrappedContext); Graphics2DImagePainter painter - = createGrapics2DImagePainter(bridgeContext, root, imageSize); + = createGraphics2DImagePainter(bridgeContext, root, imageSize); // Create AFPObjectAreaInfo RendererContextWrapper rctx = RendererContext.wrapRendererContext(rendererContext); @@ -217,8 +217,8 @@ public class AFPSVGHandler extends AbstractGenericSVGHandler { context.setProperty(AFPRendererContextConstants.AFP_GRAYSCALE, Boolean.FALSE); } - /** {@inheritDoc} */ - protected Graphics2DImagePainter createGrapics2DImagePainter(BridgeContext ctx, GraphicsNode root, Dimension imageSize) { + private Graphics2DImagePainter createGraphics2DImagePainter(BridgeContext ctx, + GraphicsNode root, Dimension imageSize) { Graphics2DImagePainter painter = null; if (paintAsBitmap()) { // paint as IOCA Image diff --git a/src/java/org/apache/fop/render/afp/AbstractAFPImageHandlerRawStream.java b/src/java/org/apache/fop/render/afp/AbstractAFPImageHandlerRawStream.java index ae8ac9950..247ab6d6f 100644 --- a/src/java/org/apache/fop/render/afp/AbstractAFPImageHandlerRawStream.java +++ b/src/java/org/apache/fop/render/afp/AbstractAFPImageHandlerRawStream.java @@ -19,33 +19,55 @@ package org.apache.fop.render.afp; +import java.awt.Rectangle; import java.io.IOException; import java.io.InputStream; import org.apache.commons.io.IOUtils; + +import org.apache.xmlgraphics.image.loader.Image; +import org.apache.xmlgraphics.image.loader.ImageSize; +import org.apache.xmlgraphics.image.loader.impl.ImageRawStream; + import org.apache.fop.afp.AFPDataObjectInfo; import org.apache.fop.afp.AFPObjectAreaInfo; import org.apache.fop.afp.AFPPaintingState; -import org.apache.xmlgraphics.image.loader.ImageInfo; -import org.apache.xmlgraphics.image.loader.ImageSize; -import org.apache.xmlgraphics.image.loader.impl.ImageRawStream; +import org.apache.fop.render.ImageHandler; +import org.apache.fop.render.RenderingContext; /** * A base abstract AFP raw stream image handler */ -public abstract class AbstractAFPImageHandlerRawStream extends AFPImageHandler { +public abstract class AbstractAFPImageHandlerRawStream extends AFPImageHandler + implements ImageHandler { /** {@inheritDoc} */ public AFPDataObjectInfo generateDataObjectInfo( AFPRendererImageInfo rendererImageInfo) throws IOException { AFPDataObjectInfo dataObjectInfo = super.generateDataObjectInfo(rendererImageInfo); - - ImageInfo imageInfo = rendererImageInfo.getImageInfo(); - String mimeType = imageInfo.getMimeType(); - if (mimeType != null) { - dataObjectInfo.setMimeType(mimeType); - } ImageRawStream rawStream = (ImageRawStream) rendererImageInfo.getImage(); + + + updateDataObjectInfo(dataObjectInfo, rawStream); + + setAdditionalParameters(dataObjectInfo, rawStream); + return dataObjectInfo; + } + + /** + * Sets additional parameters on the image object info being built. By default, this + * method does nothing but it can be overridden to provide additional functionality. + * @param imageObjectInfo the image object info being built + * @param image the image being processed + */ + protected void setAdditionalParameters(AFPDataObjectInfo imageObjectInfo, + ImageRawStream image) { + //nop + } + + private void updateDataObjectInfo(AFPDataObjectInfo dataObjectInfo, + ImageRawStream rawStream) throws IOException { + dataObjectInfo.setMimeType(rawStream.getFlavor().getMimeType()); InputStream inputStream = rawStream.createInputStream(); try { dataObjectInfo.setData(IOUtils.toByteArray(inputStream)); @@ -62,18 +84,38 @@ public abstract class AbstractAFPImageHandlerRawStream extends AFPImageHandler { ImageSize imageSize = rawStream.getSize(); dataObjectInfo.setDataHeightRes((int) (imageSize.getDpiHorizontal() * 10)); dataObjectInfo.setDataWidthRes((int) (imageSize.getDpiVertical() * 10)); + } + + /** {@inheritDoc} */ + public void handleImage(RenderingContext context, Image image, Rectangle pos) + throws IOException { + AFPRenderingContext afpContext = (AFPRenderingContext)context; + + AFPDataObjectInfo dataObjectInfo = createDataObjectInfo(); + + // set resource information + setResourceInformation(dataObjectInfo, + image.getInfo().getOriginalURI(), + afpContext.getForeignAttributes()); + + // Positioning + dataObjectInfo.setObjectAreaInfo(createObjectAreaInfo(afpContext.getPaintingState(), pos)); // set object area info - AFPObjectAreaInfo objectAreaInfo = dataObjectInfo.getObjectAreaInfo(); - AFPRendererContext rendererContext - = (AFPRendererContext)rendererImageInfo.getRendererContext(); - AFPInfo afpInfo = rendererContext.getInfo(); - AFPPaintingState paintingState = afpInfo.getPaintingState(); + //AFPObjectAreaInfo objectAreaInfo = dataObjectInfo.getObjectAreaInfo(); + AFPPaintingState paintingState = afpContext.getPaintingState(); int resolution = paintingState.getResolution(); + AFPObjectAreaInfo objectAreaInfo = dataObjectInfo.getObjectAreaInfo(); objectAreaInfo.setWidthRes(resolution); objectAreaInfo.setHeightRes(resolution); - return dataObjectInfo; + // Image content + ImageRawStream imageStream = (ImageRawStream)image; + updateDataObjectInfo(dataObjectInfo, imageStream); + setAdditionalParameters(dataObjectInfo, imageStream); + + // Create image + afpContext.getResourceManager().createObject(dataObjectInfo); } } |