blob: e80dedd87f4b6eda149f16c8de6cbb1525b24d3d [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* $Id$ */
package org.apache.fop.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;
/**
* {@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 */
private AFPResourceManager resourceManager;
/** the painting state */
private final AFPPaintingState paintingState;
/** unit converter */
private final AFPUnitConverter unitConv;
/** the AFP datastream */
private DataStream dataStream;
/** 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 */
private List<AFPPageSetup> deferredPageSequenceExtensions
= new java.util.LinkedList<AFPPageSetup>();
/** the shading mode for filled rectangles */
private AFPShadingMode shadingMode = AFPShadingMode.COLOR;
/**
* Default constructor.
*/
public AFPDocumentHandler(IFContext context) {
super(context);
this.resourceManager = new AFPResourceManager(context.getUserAgent().getResourceResolver());
this.paintingState = new AFPPaintingState();
this.unitConv = paintingState.getUnitConverter();
}
/** {@inheritDoc} */
public boolean supportsPagesOutOfOrder() {
return false;
}
/** {@inheritDoc} */
public String getMimeType() {
return MimeConstants.MIME_AFP;
}
/** {@inheritDoc} */
public IFDocumentHandlerConfigurator getConfigurator() {
return new AFPRendererConfigurator(getUserAgent(), new AFPRendererConfigParser());
}
/** {@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} */
@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} */
@Override
public void startDocumentHeader() throws IFException {
super.startDocumentHeader();
this.location = Location.IN_DOCUMENT_HEADER;
}
/** {@inheritDoc} */
@Override
public void endDocumentHeader() throws IFException {
super.endDocumentHeader();
this.location = Location.ELSEWHERE;
}
/** {@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} */
public void startPageSequence(String id) throws IFException {
try {
if (!"false".equals(getContext().getForeignAttribute(AFPElementMapping.PAGE_GROUP))) {
dataStream.startPageGroup();
}
} catch (IOException ioe) {
throw new IFException("I/O error in startPageSequence()", ioe);
}
this.location = Location.FOLLOWING_PAGE_SEQUENCE;
}
/** {@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
*
* @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 {
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} */
@Override
public void startPageHeader() throws IFException {
super.startPageHeader();
this.location = Location.IN_PAGE_HEADER;
}
/** {@inheritDoc} */
@Override
public void endPageHeader() throws IFException {
this.location = Location.ELSEWHERE;
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 {
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
* @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
* @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} */
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 setCMYKImagesSupported(boolean value) {
paintingState.setCMYKImagesSupported(value);
}
/** {@inheritDoc} */
public void setDitheringQuality(float quality) {
this.paintingState.setDitheringQuality(quality);
}
/** {@inheritDoc} */
public void setBitmapEncodingQuality(float quality) {
this.paintingState.setBitmapEncodingQuality(quality);
}
/** {@inheritDoc} */
public void setShadingMode(AFPShadingMode shadingMode) {
this.shadingMode = shadingMode;
}
/** {@inheritDoc} */
public void setResolution(int resolution) {
paintingState.setResolution(resolution);
}
/** {@inheritDoc} */
public void setLineWidthCorrection(float correction) {
paintingState.setLineWidthCorrection(correction);
}
/** {@inheritDoc} */
public int getResolution() {
return paintingState.getResolution();
}
/** {@inheritDoc} */
public void setGOCAEnabled(boolean enabled) {
this.paintingState.setGOCAEnabled(enabled);
}
/** {@inheritDoc} */
public boolean isGOCAEnabled() {
return this.paintingState.isGOCAEnabled();
}
/** {@inheritDoc} */
public void setStrokeGOCAText(boolean stroke) {
this.paintingState.setStrokeGOCAText(stroke);
}
/** {@inheritDoc} */
public boolean isStrokeGOCAText() {
return this.paintingState.isStrokeGOCAText();
}
/** {@inheritDoc} */
public void setWrapPSeg(boolean pSeg) {
paintingState.setWrapPSeg(pSeg);
}
public void setWrapGocaPSeg(boolean pSeg) {
paintingState.setWrapGocaPSeg(pSeg);
}
/** {@inheritDoc} */
public void setFS45(boolean fs45) {
paintingState.setFS45(fs45);
}
/** {@inheritDoc} */
public boolean getWrapPSeg() {
return paintingState.getWrapPSeg();
}
/** {@inheritDoc} */
public boolean getFS45() {
return paintingState.getFS45();
}
public void setDefaultResourceGroupUri(URI uri) {
resourceManager.setDefaultResourceGroupUri(uri);
}
/** {@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.
* @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} */
public void canEmbedJpeg(boolean canEmbed) {
paintingState.setCanEmbedJpeg(canEmbed);
}
}