/* * 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; import java.awt.Color; import java.awt.Point; import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.util.Iterator; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.fop.afp.fonts.AFPFont; import org.apache.fop.afp.fonts.AFPFontAttributes; import org.apache.fop.afp.fonts.CharacterSet; import org.apache.fop.afp.modca.AbstractPageObject; import org.apache.fop.afp.modca.Document; import org.apache.fop.afp.modca.InterchangeSet; import org.apache.fop.afp.modca.Overlay; import org.apache.fop.afp.modca.PageGroup; import org.apache.fop.afp.modca.PageObject; import org.apache.fop.afp.modca.ResourceGroup; import org.apache.fop.afp.modca.TagLogicalElement; import org.apache.fop.afp.modca.triplets.FullyQualifiedNameTriplet; import org.apache.fop.afp.ptoca.PtocaBuilder; import org.apache.fop.afp.ptoca.PtocaProducer; import org.apache.fop.fonts.Font; import org.apache.fop.util.CharUtilities; /** * A data stream is a continuous ordered stream of data elements and objects * conforming to a given format. Application programs can generate data streams * destined for a presentation service, archive library, presentation device or * another application program. The strategic presentation data stream * architectures used is Mixed Object Document Content Architecture (MO:DCA). * * The MO:DCA architecture defines the data stream used by applications to * describe documents and object envelopes for interchange with other * applications and application services. Documents defined in the MO:DCA format * may be archived in a database, then later retrieved, viewed, annotated and * printed in local or distributed systems environments. Presentation fidelity * is accommodated by including resource objects in the documents that reference * them. */ public class DataStream { /** Static logging instance */ protected static final Log LOG = LogFactory.getLog("org.apache.xmlgraphics.afp"); /** Boolean completion indicator */ private boolean complete; /** The AFP document object */ private Document document; /** The current page group object */ private PageGroup currentPageGroup; /** The current page object */ private PageObject currentPageObject; /** The current overlay object */ private Overlay currentOverlay; /** The current page */ private AbstractPageObject currentPage; /** The MO:DCA interchange set in use (default to MO:DCA-P IS/2 set) */ private InterchangeSet interchangeSet = InterchangeSet.valueOf(InterchangeSet.MODCA_PRESENTATION_INTERCHANGE_SET_2); private final Factory factory; private OutputStream outputStream; /** the afp painting state */ private final AFPPaintingState paintingState; /** * Default constructor for the AFPDocumentStream. * * @param factory the resource factory * @param paintingState the AFP painting state * @param outputStream the outputstream to write to */ public DataStream(Factory factory, AFPPaintingState paintingState, OutputStream outputStream) { this.paintingState = paintingState; this.factory = factory; this.outputStream = outputStream; } /** * Returns the outputstream * * @return the outputstream */ public OutputStream getOutputStream() { return this.outputStream; } /** * Returns the document object * * @return the document object */ private Document getDocument() { return this.document; } /** * Returns the current page * * @return the current page */ public AbstractPageObject getCurrentPage() { return this.currentPage; } /** * The document is started by invoking this method which creates an instance * of the AFP Document object. * * @param name * the name of this document. */ public void setDocumentName(String name) { if (name != null) { getDocument().setFullyQualifiedName( FullyQualifiedNameTriplet.TYPE_BEGIN_DOCUMENT_REF, FullyQualifiedNameTriplet.FORMAT_CHARSTR, name); } } /** * Helper method to mark the end of the current document. * * @throws IOException thrown if an I/O exception of some sort has occurred */ public void endDocument() throws IOException { if (complete) { String msg = "Invalid state - document already ended."; LOG.warn("endDocument():: " + msg); throw new IllegalStateException(msg); } if (currentPageObject != null) { // End the current page if necessary endPage(); } if (currentPageGroup != null) { // End the current page group if necessary endPageGroup(); } // Write out document if (document != null) { document.endDocument(); document.writeToStream(this.outputStream); } this.outputStream.flush(); this.complete = true; this.document = null; this.outputStream = null; } /** * Start a new page. When processing has finished on the current page, the * {@link #endPage()}method must be invoked to mark the page ending. * * @param pageWidth * the width of the page * @param pageHeight * the height of the page * @param pageRotation * the rotation of the page * @param pageWidthRes * the width resolution of the page * @param pageHeightRes * the height resolution of the page */ public void startPage(int pageWidth, int pageHeight, int pageRotation, int pageWidthRes, int pageHeightRes) { currentPageObject = factory.createPage(pageWidth, pageHeight, pageRotation, pageWidthRes, pageHeightRes); currentPage = currentPageObject; currentOverlay = null; } /** * Start a new overlay. When processing has finished on the current overlay, * the {@link #endOverlay()}method must be invoked to mark the overlay * ending. * * @param x * the x position of the overlay on the page * @param y * the y position of the overlay on the page * @param width * the width of the overlay * @param height * the height of the overlay * @param widthRes * the width resolution of the overlay * @param heightRes * the height resolution of the overlay * @param overlayRotation * the rotation of the overlay */ public void startOverlay(int x, int y, int width, int height, int widthRes, int heightRes, int overlayRotation) { this.currentOverlay = factory.createOverlay( width, height, widthRes, heightRes, overlayRotation); String overlayName = currentOverlay.getName(); currentPageObject.createIncludePageOverlay(overlayName, x, y, 0); currentPage = currentOverlay; } /** * Helper method to mark the end of the current overlay. * * @throws IOException thrown if an I/O exception of some sort has occurred */ public void endOverlay() throws IOException { if (currentOverlay != null) { currentOverlay.endPage(); currentOverlay = null; currentPage = currentPageObject; } } /** * Helper method to save the current page. * * @return current page object that was saved */ public PageObject savePage() { PageObject pageObject = currentPageObject; if (currentPageGroup != null) { currentPageGroup.addPage(currentPageObject); } else { document.addPage(currentPageObject); } currentPageObject = null; currentPage = null; return pageObject; } /** * Helper method to restore the current page. * * @param pageObject * page object */ public void restorePage(PageObject pageObject) { currentPageObject = pageObject; currentPage = pageObject; } /** * Helper method to mark the end of the current page. * * @throws IOException thrown if an I/O exception of some sort has occurred */ public void endPage() throws IOException { if (currentPageObject != null) { currentPageObject.endPage(); if (currentPageGroup != null) { currentPageGroup.addPage(currentPageObject); currentPageGroup.writeToStream(this.outputStream); } else { document.addPage(currentPageObject); document.writeToStream(this.outputStream); } currentPageObject = null; currentPage = null; } } /** * Creates the given page fonts in the current page * * @param pageFonts * a collection of AFP font attributes */ public void addFontsToCurrentPage(Map pageFonts) { Iterator iter = pageFonts.values().iterator(); while (iter.hasNext()) { AFPFontAttributes afpFontAttributes = (AFPFontAttributes) iter .next(); createFont(afpFontAttributes.getFontReference(), afpFontAttributes .getFont(), afpFontAttributes.getPointSize()); } } /** * Helper method to create a map coded font object on the current page, this * method delegates the construction of the map coded font object to the * active environment group on the current page. * * @param fontReference * the font number used as the resource identifier * @param font * the font * @param size * the point size of the font */ public void createFont(int fontReference, AFPFont font, int size) { currentPage.createFont(fontReference, font, size); } /** * Returns a point on the current page * * @param x the X-coordinate * @param y the Y-coordinate * @return a point on the current page */ private Point getPoint(int x, int y) { return paintingState.getPoint(x, y); } /** * Helper method to create text on the current page, this method delegates * to the current presentation text object in order to construct the text. * * @param textDataInfo the afp text data * @param letterSpacing letter spacing to draw text with * @param wordSpacing word Spacing to draw text with * @param font is the font to draw text with * @param charSet is the AFP Character Set to use with the text * @throws UnsupportedEncodingException thrown if character encoding is not supported */ public void createText(final AFPTextDataInfo textDataInfo, final int letterSpacing, final int wordSpacing, final Font font, final CharacterSet charSet) throws UnsupportedEncodingException { int rotation = paintingState.getRotation(); if (rotation != 0) { textDataInfo.setRotation(rotation); Point p = getPoint(textDataInfo.getX(), textDataInfo.getY()); textDataInfo.setX(p.x); textDataInfo.setY(p.y); } // use PtocaProducer to create PTX records PtocaProducer producer = new PtocaProducer() { public void produce(PtocaBuilder builder) throws IOException { builder.setTextOrientation(textDataInfo.getRotation()); builder.absoluteMoveBaseline(textDataInfo.getY()); builder.absoluteMoveInline(textDataInfo.getX()); builder.setExtendedTextColor(textDataInfo.getColor()); builder.setCodedFont((byte)textDataInfo.getFontReference()); int l = textDataInfo.getString().length(); StringBuffer sb = new StringBuffer(); int interCharacterAdjustment = 0; AFPUnitConverter unitConv = paintingState.getUnitConverter(); if (letterSpacing != 0) { interCharacterAdjustment = Math.round(unitConv.mpt2units(letterSpacing)); } builder.setInterCharacterAdjustment(interCharacterAdjustment); int spaceWidth = font.getCharWidth(CharUtilities.SPACE); int spacing = spaceWidth + letterSpacing; int fixedSpaceCharacterIncrement = Math.round(unitConv.mpt2units(spacing)); int varSpaceCharacterIncrement = fixedSpaceCharacterIncrement; if (wordSpacing != 0) { varSpaceCharacterIncrement = Math.round(unitConv.mpt2units( spaceWidth + wordSpacing + letterSpacing)); } builder.setVariableSpaceCharacterIncrement(varSpaceCharacterIncrement); boolean fixedSpaceMode = false; for (int i = 0; i < l; i++) { char orgChar = textDataInfo.getString().charAt(i); float glyphAdjust = 0; if (CharUtilities.isFixedWidthSpace(orgChar)) { flushText(builder, sb, charSet); builder.setVariableSpaceCharacterIncrement( fixedSpaceCharacterIncrement); fixedSpaceMode = true; sb.append(CharUtilities.SPACE); int charWidth = font.getCharWidth(orgChar); glyphAdjust += (charWidth - spaceWidth); } else { if (fixedSpaceMode) { flushText(builder, sb, charSet); builder.setVariableSpaceCharacterIncrement( varSpaceCharacterIncrement); fixedSpaceMode = false; } char ch; if (orgChar == CharUtilities.NBSPACE) { ch = ' '; //converted to normal space to allow word spacing } else { ch = orgChar; } sb.append(ch); } if (glyphAdjust != 0) { flushText(builder, sb, charSet); int increment = Math.round(unitConv.mpt2units(glyphAdjust)); builder.relativeMoveInline(increment); } } flushText(builder, sb, charSet); } private void flushText(PtocaBuilder builder, StringBuffer sb, final CharacterSet charSet) throws IOException { if (sb.length() > 0) { builder.addTransparentData(charSet.encodeChars(sb)); sb.setLength(0); } } }; currentPage.createText(producer); } /** * Method to create a line on the current page. * * @param lineDataInfo the line data information. */ public void createLine(AFPLineDataInfo lineDataInfo) { currentPage.createLine(lineDataInfo); } /** * This method will create shading on the page using the specified * coordinates (the shading contrast is controlled via the red, green, blue * parameters, by converting this to grey scale). * * @param x * the x coordinate of the shading * @param y * the y coordinate of the shading * @param w * the width of the shaded area * @param h * the height of the shaded area * @param col * the shading color */ public void createShading(int x, int y, int w, int h, Color col) { currentPageObject.createShading(x, y, w, h, col.getRed(), col.getGreen(), col.getBlue()); } /** * Helper method which allows creation of the MPO object, via the AEG. And * the IPO via the Page. (See actual object for descriptions.) * * @param name * the name of the static overlay * @param x x-coordinate * @param y y-coordinate */ public void createIncludePageOverlay(String name, int x, int y) { currentPageObject.createIncludePageOverlay(name, x, y, paintingState.getRotation()); currentPageObject.getActiveEnvironmentGroup().createOverlay(name); } /** * Helper method which allows creation of the IMM object. * * @param name * the name of the medium map */ public void createInvokeMediumMap(String name) { currentPageGroup.createInvokeMediumMap(name); } /** * Creates an IncludePageSegment on the current page. * * @param name * the name of the include page segment * @param x * the x coordinate for the overlay * @param y * the y coordinate for the overlay * @param width * the width of the image * @param height * the height of the image */ public void createIncludePageSegment(String name, int x, int y, int width, int height) { int xOrigin; int yOrigin; int orientation = paintingState.getRotation(); switch (orientation) { case 90: xOrigin = x - height; yOrigin = y; break; case 180: xOrigin = x - width; yOrigin = y - height; break; case 270: xOrigin = x; yOrigin = y - width; break; default: xOrigin = x; yOrigin = y; break; } boolean createHardPageSegments = true; currentPage.createIncludePageSegment(name, xOrigin, yOrigin, createHardPageSegments); } /** * Creates a TagLogicalElement on the current page. * * @param attributes * the array of key value pairs. */ public void createPageTagLogicalElement(TagLogicalElement.State[] attributes) { for (int i = 0; i < attributes.length; i++) { currentPage.createTagLogicalElement(attributes[i]); } } /** * Creates a TagLogicalElement on the current page group. * * @param attributes * the array of key value pairs. */ public void createPageGroupTagLogicalElement(TagLogicalElement.State[] attributes) { for (int i = 0; i < attributes.length; i++) { currentPageGroup.createTagLogicalElement(attributes[i]); } } /** * Creates a TagLogicalElement on the current page or page group * * @param name * The tag name * @param value * The tag value * @param encoding The CCSID character set encoding */ public void createTagLogicalElement(String name, String value, int encoding) { TagLogicalElement.State tleState = new TagLogicalElement.State(name, value, encoding); if (currentPage != null) { currentPage.createTagLogicalElement(tleState); } else { currentPageGroup.createTagLogicalElement(tleState); } } /** * Creates a NoOperation item * * @param content * byte data */ public void createNoOperation(String content) { if (currentPage != null) { currentPage.createNoOperation(content); } else if (currentPageGroup != null) { currentPageGroup.createNoOperation(content); } else { document.createNoOperation(content); } } /** * Returns the current page group * * @return the current page group */ public PageGroup getCurrentPageGroup() { return this.currentPageGroup; } /** * Start a new document. * * @throws IOException thrown if an I/O exception of some sort has occurred */ public void startDocument() throws IOException { this.document = factory.createDocument(); document.writeToStream(this.outputStream); } /** * Start a new page group. When processing has finished on the current page * group the {@link #endPageGroup()}method must be invoked to mark the page * group ending. * * @throws IOException thrown if an I/O exception of some sort has occurred */ public void startPageGroup() throws IOException { endPageGroup(); this.currentPageGroup = factory.createPageGroup(); } /** * Helper method to mark the end of the page group. * * @throws IOException thrown if an I/O exception of some sort has occurred */ public void endPageGroup() throws IOException { if (currentPageGroup != null) { currentPageGroup.endPageGroup(); document.addPageGroup(currentPageGroup); currentPageGroup = null; } document.writeToStream(outputStream); //Flush objects } /** * Sets the MO:DCA interchange set to use * * @param interchangeSet the MO:DCA interchange set */ public void setInterchangeSet(InterchangeSet interchangeSet) { this.interchangeSet = interchangeSet; } /** * Returns the MO:DCA interchange set in use * * @return the MO:DCA interchange set in use */ public InterchangeSet getInterchangeSet() { return this.interchangeSet; } /** * Returns the resource group for a given resource info * * @param level a resource level * @return a resource group for the given resource info */ public ResourceGroup getResourceGroup(AFPResourceLevel level) { ResourceGroup resourceGroup = null; if (level.isDocument()) { resourceGroup = document.getResourceGroup(); } else if (level.isPageGroup()) { resourceGroup = currentPageGroup.getResourceGroup(); } else if (level.isPage()) { resourceGroup = currentPageObject.getResourceGroup(); } return resourceGroup; } }