/*
 * 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: DataStream.java 1761021 2016-09-16 11:40:57Z ssteiner $ */

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.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.
/** * 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
/** Static logging instance */
protected static final Log LOG = LogFactory.getLog("org.apache.xmlgraphics.afp");
Boolean completion indicator
/** Boolean completion indicator */
private boolean complete;
The AFP document object
/** The AFP document object */
private Document document;
The current page group object
/** The current page group object */
private PageGroup currentPageGroup;
The current page object
/** The current page object */
private PageObject currentPageObject;
The current overlay object
/** The current overlay object */
private Overlay currentOverlay;
The current page
/** The current page */
private AbstractPageObject currentPage;
The MO:DCA interchange set in use (default to MO:DCA-P IS/2 set)
/** 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
/** the afp painting state */
private final AFPPaintingState paintingState;
Default constructor for the AFPDocumentStream.
Params:
  • factory – the resource factory
  • paintingState – the AFP painting state
  • outputStream – the outputstream to write to
/** * 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
Returns:the outputstream
/** * Returns the outputstream * * @return the outputstream */
public OutputStream getOutputStream() { return this.outputStream; }
Returns the document object
Returns:the document object
/** * Returns the document object * * @return the document object */
private Document getDocument() { return this.document; }
Returns the current page
Returns:the current page
/** * 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.
Params:
  • name – the name of this document.
/** * 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
/** * 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 endPage()method must be invoked to mark the page ending.
Params:
  • pageWidth – the width of the page
  • pageHeight – the height of the page
  • pageRotation – the rotation of the page
  • pageWidthRes – the width resolution of the page
  • pageHeightRes – the height resolution of the page
/** * 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 endOverlay()method must be invoked to mark the overlay ending.
Params:
  • x – the x position of the overlay on the page
  • y – the y position of the overlay on the page
  • width – the width of the overlay
  • height – the height of the overlay
  • widthRes – the width resolution of the overlay
  • heightRes – the height resolution of the overlay
  • overlayRotation – the rotation of the overlay
/** * 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
/** * 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.
Returns:current page object that was saved
/** * 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.
Params:
  • pageObject – page object
/** * 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
/** * 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
Params:
  • pageFonts – a collection of AFP font attributes
/** * Creates the given page fonts in the current page * * @param pageFonts * a collection of AFP font attributes */
public void addFontsToCurrentPage(Map pageFonts) { for (Object o : pageFonts.values()) { AFPFontAttributes afpFontAttributes = (AFPFontAttributes) o; 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.
Params:
  • fontReference – the font number used as the resource identifier
  • font – the font
  • size – the point size of the font
/** * 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
Params:
  • x – the X-coordinate
  • y – the Y-coordinate
Returns:a point on the current page
/** * 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.
Params:
  • textDataInfo – the afp text data
  • letterSpacing – letter spacing to draw text with
  • wordSpacing – word Spacing to draw text with
  • font – is the font to draw text with
  • charSet – is the AFP Character Set to use with the text
Throws:
/** * 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.
Params:
  • lineDataInfo – the line data information.
/** * 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).
Params:
  • x – the x coordinate of the shading
  • y – the y coordinate of the shading
  • w – the width of the shaded area
  • h – the height of the shaded area
  • col – the shading color
/** * 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.)
Params:
  • name – the name of the static overlay
  • x – x-coordinate
  • y – y-coordinate
/** * 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.
Params:
  • name – the name of the medium map
/** * 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.
Params:
  • name – the name of the include page segment
  • x – the x coordinate for the overlay
  • y – the y coordinate for the overlay
  • width – the width of the image
  • height – the height of the image
/** * 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.
Params:
  • attributes – the array of key value pairs.
/** * Creates a TagLogicalElement on the current page. * * @param attributes * the array of key value pairs. */
public void createPageTagLogicalElement(TagLogicalElement.State[] attributes) { for (TagLogicalElement.State attribute : attributes) { currentPage.createTagLogicalElement(attribute); } }
Creates a TagLogicalElement on the current page group.
Params:
  • attributes – the array of key value pairs.
/** * Creates a TagLogicalElement on the current page group. * * @param attributes * the array of key value pairs. */
public void createPageGroupTagLogicalElement(TagLogicalElement.State[] attributes) { for (TagLogicalElement.State attribute : attributes) { currentPageGroup.createTagLogicalElement(attribute); } }
Creates a TagLogicalElement on the current page or page group
Params:
  • name – The tag name
  • value – The tag value
  • encoding – The CCSID character set encoding
/** * 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
Params:
  • content – byte data
/** * 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
Returns:the current page group
/** * 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
/** * 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 endPageGroup()method must be invoked to mark the page group ending.
Throws:
  • IOException – thrown if an I/O exception of some sort has occurred
/** * 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
/** * 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
Params:
  • interchangeSet – the MO:DCA interchange set
/** * 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
Returns:the MO:DCA interchange set in use
/** * 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
Params:
  • level – a resource level
Returns:a resource group for the given resource info
/** * 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; } }