/*
 * 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: AFPDocumentHandler.java 1694450 2015-08-06 10:59:51Z ssteiner $ */

package org.apache.fop.render.afp;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.geom.AffineTransform;
import java.io.IOException;
import java.net.URI;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import org.apache.fop.afp.AFPDitheredRectanglePainter;
import org.apache.fop.afp.AFPPaintingState;
import org.apache.fop.afp.AFPRectanglePainter;
import org.apache.fop.afp.AFPResourceLevelDefaults;
import org.apache.fop.afp.AFPResourceManager;
import org.apache.fop.afp.AFPUnitConverter;
import org.apache.fop.afp.AbstractAFPPainter;
import org.apache.fop.afp.DataStream;
import org.apache.fop.afp.fonts.AFPFontCollection;
import org.apache.fop.afp.fonts.AFPPageFonts;
import org.apache.fop.afp.modca.ResourceObject;
import org.apache.fop.afp.util.AFPResourceAccessor;
import org.apache.fop.apps.MimeConstants;
import org.apache.fop.fonts.FontCollection;
import org.apache.fop.fonts.FontEventAdapter;
import org.apache.fop.fonts.FontInfo;
import org.apache.fop.fonts.FontManager;
import org.apache.fop.render.afp.AFPRendererConfig.AFPRendererConfigParser;
import org.apache.fop.render.afp.extensions.AFPElementMapping;
import org.apache.fop.render.afp.extensions.AFPIncludeFormMap;
import org.apache.fop.render.afp.extensions.AFPInvokeMediumMap;
import org.apache.fop.render.afp.extensions.AFPPageOverlay;
import org.apache.fop.render.afp.extensions.AFPPageSegmentElement;
import org.apache.fop.render.afp.extensions.AFPPageSetup;
import org.apache.fop.render.afp.extensions.ExtensionPlacement;
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;

IFDocumentHandler implementation that produces AFP (MO:DCA).
/** * {@link org.apache.fop.render.intermediate.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
/** the resource manager */
private AFPResourceManager resourceManager;
the painting state
/** the painting state */
private final AFPPaintingState paintingState;
unit converter
/** unit converter */
private final AFPUnitConverter unitConv;
the AFP datastream
/** the AFP datastream */
private DataStream dataStream;
the map of page segments
/** the map of page segments */
private Map<String, PageSegmentDescriptor> pageSegmentMap = new java.util.HashMap<String, PageSegmentDescriptor>(); // Rounded corners are cached at the document level private Map<String, String> roundedCornerNameCache = new HashMap<String, String>(); private int roundedCornerCount; private static enum Location { ELSEWHERE, IN_DOCUMENT_HEADER, FOLLOWING_PAGE_SEQUENCE, IN_PAGE_HEADER } private Location location = Location.ELSEWHERE;
temporary holds extensions that have to be deferred until the end of the page-sequence
/** temporary holds extensions that have to be deferred until the end of the page-sequence */
private List<AFPPageSetup> deferredPageSequenceExtensions = new java.util.LinkedList<AFPPageSetup>();
the shading mode for filled rectangles
/** the shading mode for filled rectangles */
private AFPShadingMode shadingMode = AFPShadingMode.COLOR;
Default constructor.
/** * Default constructor. */
public AFPDocumentHandler(IFContext context) { super(context); this.resourceManager = new AFPResourceManager(context.getUserAgent().getResourceResolver()); this.paintingState = new AFPPaintingState(); this.unitConv = paintingState.getUnitConverter(); }
{@inheritDoc}
/** {@inheritDoc} */
public boolean supportsPagesOutOfOrder() { return false; }
{@inheritDoc}
/** {@inheritDoc} */
public String getMimeType() { return MimeConstants.MIME_AFP; }
{@inheritDoc}
/** {@inheritDoc} */
public IFDocumentHandlerConfigurator getConfigurator() { return new AFPRendererConfigurator(getUserAgent(), new AFPRendererConfigParser()); }
{@inheritDoc}
/** {@inheritDoc} */
@Override public void setDefaultFontInfo(FontInfo fontInfo) { FontManager fontManager = getUserAgent().getFontManager(); FontCollection[] fontCollections = new FontCollection[] { new AFPFontCollection(getUserAgent().getEventBroadcaster(), null) }; FontInfo fi = (fontInfo != null ? fontInfo : new FontInfo()); fi.setEventListener(new FontEventAdapter(getUserAgent().getEventBroadcaster())); fontManager.setup(fi, fontCollections); setFontInfo(fi); } AFPPaintingState getPaintingState() { return this.paintingState; } DataStream getDataStream() { return this.dataStream; } AFPResourceManager getResourceManager() { return this.resourceManager; } AbstractAFPPainter createRectanglePainter() { if (AFPShadingMode.DITHERED.equals(this.shadingMode)) { return new AFPDitheredRectanglePainter( getPaintingState(), getDataStream(), getResourceManager()); } else { return new AFPRectanglePainter( getPaintingState(), getDataStream()); } }
{@inheritDoc}
/** {@inheritDoc} */
@Override public void startDocument() throws IFException { super.startDocument(); try { 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}
/** {@inheritDoc} */
@Override public void startDocumentHeader() throws IFException { super.startDocumentHeader(); this.location = Location.IN_DOCUMENT_HEADER; }
{@inheritDoc}
/** {@inheritDoc} */
@Override public void endDocumentHeader() throws IFException { super.endDocumentHeader(); this.location = Location.ELSEWHERE; }
{@inheritDoc}
/** {@inheritDoc} */
@Override 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}
/** {@inheritDoc} */
public void startPageSequence(String id) throws IFException { try { dataStream.startPageGroup(); } catch (IOException ioe) { throw new IFException("I/O error in startPageSequence()", ioe); } this.location = Location.FOLLOWING_PAGE_SEQUENCE; }
{@inheritDoc}
/** {@inheritDoc} */
public void endPageSequence() throws IFException { try { //Process deferred page-sequence-level extensions Iterator<AFPPageSetup> iter = this.deferredPageSequenceExtensions.iterator(); while (iter.hasNext()) { AFPPageSetup aps = iter.next(); iter.remove(); if (AFPElementMapping.NO_OPERATION.equals(aps.getElementName())) { handleNOP(aps); } else { throw new UnsupportedOperationException("Don't know how to handle " + aps); } } //End page sequence dataStream.endPageGroup(); } catch (IOException ioe) { throw new IFException("I/O error in endPageSequence()", ioe); } this.location = Location.ELSEWHERE; }
Returns the base AFP transform
Returns:the base AFP transform
/** * 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}
/** {@inheritDoc} */
public void startPage(int index, String name, String pageMasterName, Dimension size) throws IFException { this.location = Location.ELSEWHERE; 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); }
{@inheritDoc}
/** {@inheritDoc} */
@Override public void startPageHeader() throws IFException { super.startPageHeader(); this.location = Location.IN_PAGE_HEADER; }
{@inheritDoc}
/** {@inheritDoc} */
@Override public void endPageHeader() throws IFException { this.location = Location.ELSEWHERE; super.endPageHeader(); }
{@inheritDoc}
/** {@inheritDoc} */
public IFPainter startPageContent() throws IFException { return new AFPPainter(this); }
{@inheritDoc}
/** {@inheritDoc} */
public void endPageContent() throws IFException { }
{@inheritDoc}
/** {@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}
/** {@inheritDoc} */
public void handleExtensionObject(Object extension) throws IFException { if (extension instanceof AFPPageSetup) { AFPPageSetup aps = (AFPPageSetup)extension; String element = aps.getElementName(); if (AFPElementMapping.TAG_LOGICAL_ELEMENT.equals(element)) { switch (this.location) { case FOLLOWING_PAGE_SEQUENCE: case IN_PAGE_HEADER: String name = aps.getName(); String value = aps.getValue(); int encoding = aps.getEncoding(); dataStream.createTagLogicalElement(name, value, encoding); break; default: throw new IFException( "TLE extension must be in the page header or between page-sequence" + " and the first page: " + aps, null); } } else if (AFPElementMapping.NO_OPERATION.equals(element)) { switch (this.location) { case FOLLOWING_PAGE_SEQUENCE: if (aps.getPlacement() == ExtensionPlacement.BEFORE_END) { this.deferredPageSequenceExtensions.add(aps); break; } case IN_DOCUMENT_HEADER: case IN_PAGE_HEADER: handleNOP(aps); break; default: throw new IFException( "NOP extension must be in the document header, the page header" + " or between page-sequence" + " and the first page: " + aps, null); } } else { if (this.location != Location.IN_PAGE_HEADER) { throw new IFException( "AFP page setup extension encountered outside the page header: " + aps, null); } if (AFPElementMapping.INCLUDE_PAGE_SEGMENT.equals(element)) { AFPPageSegmentElement.AFPPageSegmentSetup apse = (AFPPageSegmentElement.AFPPageSegmentSetup)aps; String name = apse.getName(); String source = apse.getValue(); String uri = apse.getResourceSrc(); pageSegmentMap.put(source, new PageSegmentDescriptor(name, uri)); } } } else if (extension instanceof AFPPageOverlay) { AFPPageOverlay ipo = (AFPPageOverlay)extension; if (this.location != Location.IN_PAGE_HEADER) { throw new IFException( "AFP page overlay extension encountered outside the page header: " + ipo, null); } String overlay = ipo.getName(); if (overlay != null) { dataStream.createIncludePageOverlay(overlay, ipo.getX(), ipo.getY()); } } else if (extension instanceof AFPInvokeMediumMap) { if (this.location != Location.FOLLOWING_PAGE_SEQUENCE && this.location != Location.IN_PAGE_HEADER) { throw new IFException( "AFP IMM extension must be between page-sequence" + " and the first page or child of page-header: " + extension, null); } AFPInvokeMediumMap imm = (AFPInvokeMediumMap)extension; String mediumMap = imm.getName(); if (mediumMap != null) { dataStream.createInvokeMediumMap(mediumMap); } } else if (extension instanceof AFPIncludeFormMap) { AFPIncludeFormMap formMap = (AFPIncludeFormMap)extension; AFPResourceAccessor accessor = new AFPResourceAccessor( getUserAgent().getResourceResolver()); try { getResourceManager().createIncludedResource(formMap.getName(), formMap.getSrc(), accessor, ResourceObject.TYPE_FORMDEF, false, null); } catch (IOException ioe) { throw new IFException( "I/O error while embedding form map resource: " + formMap.getName(), ioe); } } }
Corner images can be reused by storing at the document level in the AFP The cache is used to map cahced images to caller generated descriptions of the corner
Params:
  • cornerKey – caller's identifier for the corner
Returns:document id of the corner image
/** * Corner images can be reused by storing at the document level in the AFP * The cache is used to map cahced images to caller generated descriptions of the corner * @param cornerKey caller's identifier for the corner * @return document id of the corner image */
public String cacheRoundedCorner(String cornerKey) { // Make a unique id StringBuffer idBuilder = new StringBuffer("RC"); String tmp = Integer.toHexString(roundedCornerCount).toUpperCase(Locale.ENGLISH); if (tmp.length() > 6) { //Will never happen //log.error("Rounded corners cache capacity exceeded"); //We should get a visual clue roundedCornerCount = 0; tmp = "000000"; } else if (tmp.length() < 6) { for (int i = 0; i < 6 - tmp.length(); i++) { idBuilder.append("0"); } idBuilder.append(tmp); } roundedCornerCount++; String id = idBuilder.toString(); //cache the corner id roundedCornerNameCache.put(cornerKey, id); return id; }
This method returns the an id that identifies a cached corner or null if non existent
Params:
  • cornerKey – caller's identifier for the corner
Returns:document id of the corner image
/** * This method returns the an id that identifies a cached corner or null if non existent * @param cornerKey caller's identifier for the corner * @return document id of the corner image */
public String getCachedRoundedCorner(String cornerKey) { return roundedCornerNameCache.get(cornerKey); } private void handleNOP(AFPPageSetup nop) { String content = nop.getContent(); if (content != null) { dataStream.createNoOperation(content); } } // ---=== AFPCustomizable ===---
{@inheritDoc}
/** {@inheritDoc} */
public void setBitsPerPixel(int bitsPerPixel) { paintingState.setBitsPerPixel(bitsPerPixel); }
{@inheritDoc}
/** {@inheritDoc} */
public void setColorImages(boolean colorImages) { paintingState.setColorImages(colorImages); }
{@inheritDoc}
/** {@inheritDoc} */
public void setNativeImagesSupported(boolean nativeImages) { paintingState.setNativeImagesSupported(nativeImages); }
{@inheritDoc}
/** {@inheritDoc} */
public void setCMYKImagesSupported(boolean value) { paintingState.setCMYKImagesSupported(value); }
{@inheritDoc}
/** {@inheritDoc} */
public void setDitheringQuality(float quality) { this.paintingState.setDitheringQuality(quality); }
{@inheritDoc}
/** {@inheritDoc} */
public void setBitmapEncodingQuality(float quality) { this.paintingState.setBitmapEncodingQuality(quality); }
{@inheritDoc}
/** {@inheritDoc} */
public void setShadingMode(AFPShadingMode shadingMode) { this.shadingMode = shadingMode; }
{@inheritDoc}
/** {@inheritDoc} */
public void setResolution(int resolution) { paintingState.setResolution(resolution); }
{@inheritDoc}
/** {@inheritDoc} */
public void setLineWidthCorrection(float correction) { paintingState.setLineWidthCorrection(correction); }
{@inheritDoc}
/** {@inheritDoc} */
public int getResolution() { return paintingState.getResolution(); }
{@inheritDoc}
/** {@inheritDoc} */
public void setGOCAEnabled(boolean enabled) { this.paintingState.setGOCAEnabled(enabled); }
{@inheritDoc}
/** {@inheritDoc} */
public boolean isGOCAEnabled() { return this.paintingState.isGOCAEnabled(); }
{@inheritDoc}
/** {@inheritDoc} */
public void setStrokeGOCAText(boolean stroke) { this.paintingState.setStrokeGOCAText(stroke); }
{@inheritDoc}
/** {@inheritDoc} */
public boolean isStrokeGOCAText() { return this.paintingState.isStrokeGOCAText(); }
{@inheritDoc}
/** {@inheritDoc} */
public void setWrapPSeg(boolean pSeg) { paintingState.setWrapPSeg(pSeg); }
{@inheritDoc}
/** {@inheritDoc} */
public void setFS45(boolean fs45) { paintingState.setFS45(fs45); }
{@inheritDoc}
/** {@inheritDoc} */
public boolean getWrapPSeg() { return paintingState.getWrapPSeg(); }
{@inheritDoc}
/** {@inheritDoc} */
public boolean getFS45() { return paintingState.getFS45(); } public void setDefaultResourceGroupUri(URI uri) { resourceManager.setDefaultResourceGroupUri(uri); }
{@inheritDoc}
/** {@inheritDoc} */
public void setResourceLevelDefaults(AFPResourceLevelDefaults defaults) { resourceManager.setResourceLevelDefaults(defaults); }
Returns the page segment descriptor for a given URI if it actually represents a page segment. Otherwise, it just returns null.
Params:
  • uri – the URI that identifies the page segment
Returns:the page segment descriptor or null if there's no page segment for the given URI
/** * Returns the page segment descriptor for a given URI if it actually represents a page segment. * Otherwise, it just returns null. * @param uri the URI that identifies the page segment * @return the page segment descriptor or null if there's no page segment for the given URI */
PageSegmentDescriptor getPageSegmentNameFor(String uri) { return pageSegmentMap.get(uri); }
{@inheritDoc}
/** {@inheritDoc} */
public void canEmbedJpeg(boolean canEmbed) { paintingState.setCanEmbedJpeg(canEmbed); } }